// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:core';

import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/file_system/overlay_file_system.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:meta/meta.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(FileTest);
    defineReflectiveTests(FolderTest);
    defineReflectiveTests(OverlayResourceProviderTest);
  });
}

final _isFile = new TypeMatcher<File>();
final _isFileSystemException = new TypeMatcher<FileSystemException>();
final _isFolder = new TypeMatcher<Folder>();

@reflectiveTest
class FileTest extends OverlayTestSupport {
  @failingTest
  test_changes() {
    // TODO(brianwilkerson) Implement this.
    fail('Not tested');
  }

  test_copyTo_noOverlay() {
    File file = _file(exists: true);
    File targetFile =
        provider.getFile(baseProvider.convertPath('/foo/test.dart'));
    expect(targetFile.exists, isFalse);
    file.copyTo(file.parent.parent);
    expect(targetFile.exists, isTrue);
  }

  test_copyTo_onlyOverlay() {
    File file = _file(exists: false);
    provider.setOverlay(file.path, content: 'overlay', modificationStamp: 3);
    File targetFile =
        provider.getFile(baseProvider.convertPath('/foo/test.dart'));
    expect(targetFile.exists, isFalse);
    file.copyTo(file.parent.parent);
    expect(targetFile.exists, isTrue);
    expect(targetFile.readAsStringSync(), 'overlay');
    provider.removeOverlay(targetFile.path);
    expect(targetFile.exists, isFalse);
  }

  test_copyTo_withOverlay() {
    File file = _file(exists: true, content: 'base');
    provider.setOverlay(file.path, content: 'overlay', modificationStamp: 3);
    File targetFile =
        provider.getFile(baseProvider.convertPath('/foo/test.dart'));
    expect(targetFile.exists, isFalse);
    file.copyTo(file.parent.parent);
    expect(targetFile.exists, isTrue);
    expect(targetFile.readAsStringSync(), 'overlay');
    provider.removeOverlay(targetFile.path);
    expect(targetFile.exists, isTrue);
    expect(targetFile.readAsStringSync(), 'base');
  }

  test_createSource() {
    File file = _file(exists: true);
    Source source = file.createSource();
    expect(source, isNotNull);
    expect(source.fullName, defaultFilePath);
    expect(source.uri, new Uri.file(defaultFilePath));
  }

  test_delete_existing_withoutOverlay() {
    File file = _file(exists: true);
    expect(file.exists, isTrue);
    file.delete();
    expect(file.exists, isFalse);
  }

  test_delete_existing_withOverlay() {
    File file = _file(exists: true, withOverlay: true);
    expect(file.exists, isTrue);
    file.delete();
    expect(file.exists, isFalse);
  }

  test_delete_notExisting_withoutOverlay() {
    File file = _file(exists: false);
    expect(file.exists, isFalse);
    expect(() => file.delete(), throwsA(_isFileSystemException));
  }

  test_delete_notExisting_withOverlay() {
    File file = _file(exists: false, withOverlay: true);
    expect(file.exists, isTrue);
    file.delete();
    expect(file.exists, isFalse);
  }

  test_exists_existing_withoutOverlay() {
    File file = _file(exists: true);
    expect(file.exists, isTrue);
  }

  test_exists_existing_withOverlay() {
    File file = _file(exists: true, withOverlay: true);
    expect(file.exists, isTrue);
  }

  test_exists_notExisting_withoutOverlay() {
    File file = _file(exists: false);
    expect(file.exists, isFalse);
  }

  test_exists_notExisting_withOverlay() {
    File file = _file(exists: false, withOverlay: true);
    expect(file.exists, isTrue);
  }

  test_isOrContains_false() {
    File file = _file(exists: true);
    expect(file.isOrContains(baseProvider.convertPath('/foo/bar/a.dart')),
        isFalse);
  }

  test_isOrContains_true() {
    File file = _file(exists: true);
    expect(file.isOrContains(file.path), isTrue);
  }

  test_lengthSync_withoutOverlay() {
    File file = _file(exists: true);
    expect(file.lengthSync, 1);
  }

  test_lengthSync_withOverlay() {
    File file = _file(exists: true, withOverlay: true);
    expect(file.lengthSync, 3);
  }

  test_modificationStamp_existing_withoutOverlay() {
    File file = _file(exists: true);
    expect(file.modificationStamp, isNotNull);
  }

  test_modificationStamp_existing_withOverlay() {
    File file = _file(exists: true, withOverlay: true);
    expect(file.modificationStamp, 42);
  }

  test_modificationStamp_notExisting_withoutOverlay() {
    File file = _file(exists: false);
    expect(() => file.modificationStamp, throwsA(_isFileSystemException));
  }

  test_modificationStamp_notExisting_withOverlay() {
    File file = _file(exists: false, withOverlay: true);
    expect(file.modificationStamp, 42);
  }

  test_parent() {
    Folder parent = _file(exists: true).parent;
    expect(parent, isNotNull);
    expect(parent.exists, isTrue);
    expect(parent.path, defaultFolderPath);
  }

  test_readAsBytesSync_existing_withoutOverlay() {
    File file = _file(exists: true);
    expect(file.readAsBytesSync(), <int>[97]);
  }

  test_readAsBytesSync_existing_withOverlay() {
    File file = _file(exists: true, withOverlay: true);
    expect(file.readAsBytesSync(), <int>[98, 98, 98]);
  }

  test_readAsBytesSync_notExisting_withoutOverlay() {
    File file = _file(exists: false);
    expect(() => file.readAsBytesSync(), throwsA(_isFileSystemException));
  }

  test_readAsBytesSync_notExisting_withOverlay() {
    File file = _file(exists: false, withOverlay: true);
    expect(file.readAsBytesSync(), <int>[98, 98, 98]);
  }

  test_readAsStringSync_existing_withoutOverlay() {
    File file = _file(exists: true);
    expect(file.readAsStringSync(), 'a');
  }

  test_readAsStringSync_existing_withOverlay() {
    File file = _file(exists: true, withOverlay: true);
    expect(file.readAsStringSync(), 'bbb');
  }

  test_readAsStringSync_notExisting_withoutOverlay() {
    File file = _file(exists: false);
    expect(() {
      file.readAsStringSync();
    }, throwsA(_isFileSystemException));
  }

  test_readAsStringSync_notExisting_withOverlay() {
    File file = _file(exists: false, withOverlay: true);
    expect(file.readAsStringSync(), 'bbb');
  }

  test_renameSync_existingFile_conflictsWithFile() {
    String oldPath = '/foo/bar/file.txt';
    String newPath = baseProvider.convertPath('/foo/bar/new-file.txt');
    File oldFile = _file(content: 'old', exists: true, path: oldPath);
    File newFile = _file(content: 'new', exists: true, path: newPath);
    oldFile.renameSync(newPath);
    expect(oldFile.path, baseProvider.convertPath(oldPath));
    expect(oldFile.exists, isFalse);
    expect(newFile.path, newPath);
    expect(newFile.exists, isTrue);
    expect(newFile.readAsStringSync(), 'old');
  }

  test_renameSync_existingFile_conflictsWithFolder() {
    String oldPath = '/foo/bar/file.txt';
    String newPath = baseProvider.convertPath('/foo/bar/new-baz');
    File oldFile = _file(exists: true, path: oldPath);
    Folder newFolder = _folder(exists: true, path: newPath);
    expect(() => oldFile.renameSync(newPath), throwsA(_isFileSystemException));
    expect(oldFile.path, baseProvider.convertPath(oldPath));
    expect(oldFile.exists, isTrue);
    expect(newFolder.path, newPath);
    expect(newFolder.exists, isTrue);
  }

  test_renameSync_existingFile_withoutOverlay() {
    String oldPath = '/foo/bar/file.txt';
    String newPath = baseProvider.convertPath('/foo/bar/new-file.txt');
    File oldFile = _file(exists: true, path: oldPath);
    File newFile = oldFile.renameSync(newPath);
    expect(oldFile.path, baseProvider.convertPath(oldPath));
    expect(oldFile.exists, isFalse);
    expect(newFile.path, newPath);
    expect(newFile.exists, isTrue);
    expect(newFile.readAsStringSync(), 'a');
  }

  test_renameSync_existingFile_withOverlay() {
    String oldPath = '/foo/bar/file.txt';
    String newPath = baseProvider.convertPath('/foo/bar/new-file.txt');
    File oldFile = _file(exists: true, path: oldPath, withOverlay: true);
    File newFile = oldFile.renameSync(newPath);
    expect(oldFile.path, baseProvider.convertPath(oldPath));
    expect(oldFile.exists, isFalse);
    expect(newFile.path, newPath);
    expect(newFile.exists, isTrue);
    expect(newFile.readAsStringSync(), 'bbb');
  }

  test_renameSync_notExisting_withoutOverlay() {
    String oldPath = '/foo/bar/file.txt';
    String newPath = baseProvider.convertPath('/foo/bar/new-file.txt');
    File oldFile = _file(exists: true, path: oldPath);
    File newFile = oldFile.renameSync(newPath);
    expect(oldFile.path, baseProvider.convertPath(oldPath));
    expect(oldFile.exists, isFalse);
    expect(newFile.path, newPath);
    expect(newFile.exists, isTrue);
    expect(newFile.readAsStringSync(), 'a');
  }

  test_renameSync_notExisting_withOverlay() {
    String oldPath = '/foo/bar/file.txt';
    String newPath = baseProvider.convertPath('/foo/bar/new-file.txt');
    File oldFile = _file(exists: false, path: oldPath, withOverlay: true);
    File newFile = oldFile.renameSync(newPath);
    expect(oldFile.path, baseProvider.convertPath(oldPath));
    expect(oldFile.exists, isFalse);
    expect(newFile.path, newPath);
    expect(newFile.exists, isTrue);
    expect(newFile.readAsStringSync(), 'bbb');
  }

  @failingTest
  void test_resolveSymbolicLinksSync_links_existingFile_withoutOverlay() {
    fail('Not tested');
    // TODO(brianwilkerson) Decide how to test this given that we cannot
    // create a link in a MemoryResourceProvider.
//    // Create a file at '/temp/a/b/test.txt'.
//    String pathA = baseProvider.convertPath('/temp/a');
//    String pathB = baseProvider.convertPath('/temp/a/b');
//    baseProvider.newFolder(pathB);
//    String filePath = baseProvider.convertPath('/temp/a/b/test.txt');
//    File testFile = baseProvider.newFile(filePath, 'test');
//
//    // Create a symbolic link from '/temp/c/d' to '/temp/a'.
//    String pathC = baseProvider.convertPath('/temp/c');
//    String pathD = baseProvider.convertPath('/temp/c/d');
//    new io.Link(pathD).createSync(pathA, recursive: true);
//
//    // Create a symbolic link from '/temp/e/f' to '/temp/c'.
//    String pathE = baseProvider.convertPath('/temp/e');
//    String pathF = baseProvider.convertPath('/temp/e/f');
//    new io.Link(pathF).createSync(pathC, recursive: true);
//
//    // Resolve the path '/temp/e/f/d/b/test.txt' to '/temp/a/b/test.txt'.
//    String linkPath = baseProvider.convertPath('/temp/e/f/d/b/test.txt');
//    File file = baseProvider.getFile(linkPath);
//    expect(file.resolveSymbolicLinksSync().path,
//        testFile.resolveSymbolicLinksSync());
  }

  void test_resolveSymbolicLinksSync_noLinks_existingFile_withoutOverlay() {
    _resolveSymbolicLinksSync_noLinks(
        _file(exists: true, path: '/temp/a/b/test.txt'));
  }

  void test_resolveSymbolicLinksSync_noLinks_existingFile_withOverlay() {
    _resolveSymbolicLinksSync_noLinks(
        _file(exists: true, path: '/temp/a/b/test.txt', withOverlay: true));
  }

  void test_resolveSymbolicLinksSync_noLinks_notExisting_withoutOverlay() {
    _resolveSymbolicLinksSync_noLinks(
        _file(exists: false, path: '/temp/a/b/test.txt'));
  }

  void test_resolveSymbolicLinksSync_noLinks_notExisting_withOverlay() {
    _resolveSymbolicLinksSync_noLinks(
        _file(exists: false, path: '/temp/a/b/test.txt', withOverlay: true));
  }

  test_shortName() {
    expect(_file(exists: true).shortName, 'test.dart');
  }

  test_toUri() {
    File file = _file(exists: true);
    expect(file.toUri(), new Uri.file(file.path));
  }

  test_writeAsBytesSync_withoutOverlay() {
    File file = _file(exists: true);
    file.writeAsBytesSync(<int>[99, 99]);
    expect(file.readAsBytesSync(), <int>[99, 99]);
  }

  test_writeAsBytesSync_withOverlay() {
    File file = _file(exists: true, withOverlay: true);
    expect(() => file.writeAsBytesSync(<int>[99, 99]),
        throwsA(_isFileSystemException));
  }

  test_writeAsStringSync_withoutOverlay() {
    File file = _file(exists: true);
    file.writeAsStringSync('cc');
    expect(file.readAsStringSync(), 'cc');
  }

  test_writeAsStringSync_withOverlay() {
    File file = _file(exists: true, withOverlay: true);
    expect(() => file.writeAsStringSync('cc'), throwsA(_isFileSystemException));
  }

  void _resolveSymbolicLinksSync_noLinks(File file) {
    //
    // On some platforms the path to the temp directory includes a symbolic
    // link. We remove that from the equation before creating the File in order
    // to show that the operation works as expected without symbolic links.
    //
    file = baseProvider.getFile(file.resolveSymbolicLinksSync().path);
    expect(file.resolveSymbolicLinksSync(), file);
  }
}

@reflectiveTest
class FolderTest extends OverlayTestSupport {
  test_canonicalizePath_dot_absolute() {
    Folder folder = _folder(exists: true, path: '/foo/bar');
    expect(folder.canonicalizePath(baseProvider.convertPath('/a/b/./c')),
        equals(baseProvider.convertPath('/a/b/c')));
  }

  test_canonicalizePath_dot_relative() {
    Folder folder = _folder(exists: true, path: '/foo/bar');
    expect(folder.canonicalizePath(baseProvider.convertPath('./baz')),
        equals(baseProvider.convertPath('/foo/bar/baz')));
  }

  test_canonicalizePath_dotDot_absolute() {
    Folder folder = _folder(exists: true, path: '/foo/bar');
    expect(folder.canonicalizePath(baseProvider.convertPath('/a/b/../c')),
        equals(baseProvider.convertPath('/a/c')));
  }

  test_canonicalizePath_dotDot_relative() {
    Folder folder = _folder(exists: true, path: '/foo/bar');
    expect(folder.canonicalizePath(baseProvider.convertPath('../baz')),
        equals(baseProvider.convertPath('/foo/baz')));
  }

  test_canonicalizePath_simple_absolute() {
    Folder folder = _folder(exists: true, path: '/foo/bar');
    expect(folder.canonicalizePath(baseProvider.convertPath('/baz')),
        equals(baseProvider.convertPath('/baz')));
  }

  test_canonicalizePath_simple_relative() {
    Folder folder = _folder(exists: true, path: '/foo/bar');
    expect(folder.canonicalizePath(baseProvider.convertPath('baz')),
        equals(baseProvider.convertPath('/foo/bar/baz')));
  }

  @failingTest
  test_changes() {
    // TODO(brianwilkerson) Implement this.
    fail('Not tested');
  }

  test_contains() {
    Folder folder = _folder(exists: true);
    expect(folder.contains(defaultFilePath), isTrue);
  }

  test_copyTo() {
    String sourcePath = baseProvider.convertPath('/source');
    String subdirPath = baseProvider.convertPath('/source/subdir');
    baseProvider.newFolder(sourcePath);
    baseProvider.newFolder(subdirPath);
    baseProvider.newFile(
        baseProvider.convertPath('/source/file1.txt'), 'file1');
    baseProvider.newFile(
        baseProvider.convertPath('/source/subdir/file2.txt'), 'file2');
    Folder source = provider.getFolder(sourcePath);
    Folder destination =
        provider.getFolder(baseProvider.convertPath('/destination'));

    Folder copy = source.copyTo(destination);
    expect(copy.parent, destination);
    _verifyStructure(copy, source);
  }

  test_create() {
    Folder folder = _folder(exists: false);
    expect(folder.exists, isFalse);
    folder.create();
    expect(folder.exists, isTrue);
  }

  test_delete_existing() {
    Folder folder = _folder(exists: true);
    expect(folder.exists, isTrue);
    folder.delete();
    expect(folder.exists, isFalse);
  }

  test_delete_notExisting() {
    Folder folder = _folder(exists: false);
    expect(folder.exists, isFalse);
    expect(() => folder.delete(), throwsA(new TypeMatcher<ArgumentError>()));
  }

  test_exists_false() {
    Folder folder = _folder(exists: false);
    expect(folder.exists, isFalse);
  }

  test_exists_true() {
    Folder folder = _folder(exists: true);
    expect(folder.exists, isTrue);
  }

  test_getChild_file_existing() {
    Folder folder = _folder(exists: true);
    _file(exists: true);
    Resource child = folder.getChild(defaultFilePath);
    expect(child, _isFile);
  }

  test_getChild_file_notExisting() {
    Folder folder = _folder(exists: true);
    Resource child = folder.getChild(defaultFilePath);
    expect(child, _isFile);
  }

  test_getChild_folder() {
    Folder folder = _folder(exists: true);
    String childPath = provider.pathContext.join(folder.path, 'lib');
    _folder(exists: true, path: childPath);
    Resource child = folder.getChild(childPath);
    expect(child, _isFolder);
  }

  test_getChildAssumingFile() {
    Folder folder = _folder(exists: true);
    File child = folder.getChildAssumingFile('README.md');
    expect(child, isNotNull);
  }

  test_getChildAssumingFolder() {
    Folder folder = _folder(exists: true);
    Folder child = folder.getChildAssumingFolder('lib');
    expect(child, isNotNull);
  }

  test_getChildren() {
    Folder folder = _folder(exists: true);
    Folder child1 = _folder(
        exists: true, path: provider.pathContext.join(folder.path, 'lib'));
    _file(exists: true, path: provider.pathContext.join(child1.path, 'a.dart'));
    File child2 = _file(
        exists: true, path: provider.pathContext.join(folder.path, 'b.dart'));
    File child3 = _file(
        exists: false,
        path: provider.pathContext.join(folder.path, 'c.dart'),
        withOverlay: true);
    List<Resource> children = folder.getChildren();
    expect(children, hasLength(3));
    expect(children.map((resource) => resource.path),
        unorderedEquals([child1.path, child2.path, child3.path]));
  }

  test_isOrContains_false() {
    Folder folder = _folder(exists: true);
    expect(folder.isOrContains(baseProvider.convertPath('/foo/baz')), isFalse);
  }

  test_isOrContains_true_child() {
    Folder folder = _folder(exists: true);
    expect(folder.isOrContains(defaultFilePath), isTrue);
  }

  test_isOrContains_true_same() {
    Folder folder = _folder(exists: true);
    expect(folder.isOrContains(folder.path), isTrue);
  }

  test_parent_ofNonRoot() {
    Folder parent = _folder(exists: true).parent;
    expect(parent, isNotNull);
    expect(parent.exists, isTrue);
    expect(parent.path, baseProvider.convertPath('/foo'));
  }

  test_parent_ofRoot() {
    Folder parent = _folder(exists: true, path: '/').parent;
    expect(parent, isNull);
  }

  @failingTest
  test_resolveSymbolicLinksSync_links() {
    // TODO(brianwilkerson) Implement this.
    fail('Not tested');
  }

  void test_resolveSymbolicLinksSync_noLinks_existingFile() {
    _resolveSymbolicLinksSync_noLinks(
        _folder(exists: true, path: '/temp/a/b/test.txt'));
  }

  void test_resolveSymbolicLinksSync_noLinks_notExisting() {
    _resolveSymbolicLinksSync_noLinks(
        _folder(exists: false, path: '/temp/a/b/test.txt'));
  }

  test_shortName() {
    expect(_folder(exists: true).shortName, 'bar');
  }

  test_toUri() {
    Folder folder = _folder(exists: true);
    expect(folder.toUri(), new Uri.directory(folder.path));
  }

  void _resolveSymbolicLinksSync_noLinks(Folder folder) {
    //
    // On some platforms the path to the temp directory includes a symbolic
    // link. We remove that from the equation before creating the File in order
    // to show that the operation works as expected without symbolic links.
    //
    folder = baseProvider.getFolder(folder.resolveSymbolicLinksSync().path);
    expect(folder.resolveSymbolicLinksSync(), folder);
  }

  /**
   * Verify that the [copy] has the same name and content as the [source].
   */
  void _verifyStructure(Folder copy, Folder source) {
    expect(copy.shortName, source.shortName);
    Map<String, File> sourceFiles = <String, File>{};
    Map<String, Folder> sourceFolders = <String, Folder>{};
    for (Resource child in source.getChildren()) {
      if (child is File) {
        sourceFiles[child.shortName] = child;
      } else if (child is Folder) {
        sourceFolders[child.shortName] = child;
      } else {
        fail('Unknown class of resource: ${child.runtimeType}');
      }
    }
    Map<String, File> copyFiles = <String, File>{};
    Map<String, Folder> copyFolders = <String, Folder>{};
    for (Resource child in source.getChildren()) {
      if (child is File) {
        copyFiles[child.shortName] = child;
      } else if (child is Folder) {
        copyFolders[child.shortName] = child;
      } else {
        fail('Unknown class of resource: ${child.runtimeType}');
      }
    }
    for (String fileName in sourceFiles.keys) {
      File sourceChild = sourceFiles[fileName];
      File copiedChild = copyFiles[fileName];
      if (copiedChild == null) {
        fail('Failed to copy file ${sourceChild.path}');
      }
      expect(copiedChild.readAsStringSync(), sourceChild.readAsStringSync(),
          reason: 'Incorrectly copied file ${sourceChild.path}');
    }
    for (String fileName in sourceFolders.keys) {
      Folder sourceChild = sourceFolders[fileName];
      Folder copiedChild = copyFolders[fileName];
      if (copiedChild == null) {
        fail('Failed to copy folder ${sourceChild.path}');
      }
      _verifyStructure(copiedChild, sourceChild);
    }
  }
}

@reflectiveTest
class OverlayResourceProviderTest extends OverlayTestSupport {
  test_getFile_existing_withoutOverlay() {
    File file = _file(exists: true);
    expect(file, isNotNull);
    expect(file.path, defaultFilePath);
    expect(file.exists, isTrue);
  }

  test_getFile_existing_withOverlay() {
    File file = _file(exists: true, withOverlay: true);
    expect(file, isNotNull);
    expect(file.path, defaultFilePath);
    expect(file.exists, isTrue);
  }

  test_getFile_notExisting_withoutOverlay() {
    File file = _file(exists: false);
    expect(file, isNotNull);
    expect(file.path, defaultFilePath);
    expect(file.exists, isFalse);
  }

  test_getFile_notExisting_withOverlay() {
    File file = _file(exists: false, withOverlay: true);
    expect(file, isNotNull);
    expect(file.path, defaultFilePath);
    expect(file.exists, isTrue);
  }

  test_getFolder_existing() {
    Folder folder = _folder(exists: true);
    expect(folder, isNotNull);
    expect(folder.path, defaultFolderPath);
    expect(folder.exists, isTrue);
  }

  test_getFolder_notExisting() {
    Folder folder = _folder(exists: false);
    expect(folder, isNotNull);
    expect(folder.path, defaultFolderPath);
    expect(folder.exists, isFalse);
  }

  test_getModificationTimes_withoutOverlay() async {
    Source source = _file(exists: true).createSource();
    List<int> times = await provider.getModificationTimes([source]);
    expect(times, [source.modificationStamp]);
  }

  test_getModificationTimes_withOverlay() async {
    Source source = _file(exists: true, withOverlay: true).createSource();
    List<int> times = await provider.getModificationTimes([source]);
    expect(times, [42]);
  }

  test_getResource_existingFile_withoutOverlay() {
    String path = _file(exists: true).path;
    Resource resource = provider.getResource(path);
    expect(resource, _isFile);
  }

  test_getResource_existingFile_withOverlay() {
    String path = _file(exists: true, withOverlay: true).path;
    Resource resource = provider.getResource(path);
    expect(resource, _isFile);
  }

  test_getResource_existingFolder() {
    String path = _folder(exists: true).path;
    Resource resource = provider.getResource(path);
    expect(resource, _isFolder);
  }

  test_getResource_notExisting_withoutOverlay() {
    String path = _file(exists: false).path;
    Resource resource = provider.getResource(path);
    expect(resource, _isFile);
  }

  test_getResource_notExisting_withOverlay() {
    String path = _file(exists: false, withOverlay: true).path;
    Resource resource = provider.getResource(path);
    expect(resource, _isFile);
  }

  test_getStateLocation_uniqueness() {
    String idOne = 'one';
    Folder folderOne = provider.getStateLocation(idOne);
    expect(folderOne, isNotNull);
    String idTwo = 'two';
    Folder folderTwo = provider.getStateLocation(idTwo);
    expect(folderTwo, isNotNull);
    expect(folderTwo, isNot(equals(folderOne)));
    expect(provider.getStateLocation(idOne), equals(folderOne));
  }

  test_pathContext() {
    expect(provider.pathContext, baseProvider.pathContext);
  }
}

class OverlayTestSupport {
  MemoryResourceProvider baseProvider;
  OverlayResourceProvider provider;

  String defaultFolderPath;
  String defaultFilePath;

  void setUp() {
    baseProvider = new MemoryResourceProvider();
    provider = new OverlayResourceProvider(baseProvider);

    defaultFolderPath = baseProvider.convertPath('/foo/bar');
    defaultFilePath = baseProvider.convertPath('/foo/bar/test.dart');
  }

  File _file(
      {@required bool exists,
      String content,
      String path,
      bool withOverlay = false}) {
    if (path == null) {
      path = defaultFilePath;
    } else {
      path = baseProvider.convertPath(path);
    }
    if (exists) {
      baseProvider.newFile(path, content ?? 'a');
    }
    if (withOverlay) {
      provider.setOverlay(path, content: 'bbb', modificationStamp: 42);
    }
    return provider.getFile(path);
  }

  Folder _folder({@required bool exists, String path}) {
    if (path == null) {
      path = defaultFolderPath;
    } else {
      path = baseProvider.convertPath(path);
    }
    if (exists) {
      baseProvider.newFolder(path);
    }
    return provider.getFolder(path);
  }
}
