// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import "dart:async"; import "dart:io"; import "package:async_helper/async_helper.dart"; import "package:expect/expect.dart"; import "package:path/path.dart"; // Test the dart:io Link class. class FutureExpect { static Future isTrue(Future result) => result.then((value) => Expect.isTrue(value)); static Future isFalse(Future result) => result.then((value) => Expect.isFalse(value)); static Future equals(expected, Future result) => result.then((value) => Expect.equals(expected, value)); static Future listEquals(expected, Future result) => result.then((value) => Expect.listEquals(expected, value)); static Future throws(Future result) => result.then((value) { throw new ExpectException( "FutureExpect.throws received $value instead of an exception"); }, onError: (_) => null); } Future testCreate() { return Directory.systemTemp.createTemp('dart_link_async').then((baseDir) { if (isRelative(baseDir.path)) { Expect.fail( 'Link tests expect absolute paths to system temporary directories. ' 'A relative path in TMPDIR gives relative paths to them.'); } String base = baseDir.path; String link = join(base, 'link'); String target = join(base, 'target'); return new Directory(target) .create() .then((_) => new Link(link).create(target)) .then((_) => FutureExpect.equals( FileSystemEntityType.directory, FileSystemEntity.type(link))) .then((_) => FutureExpect.equals( FileSystemEntityType.directory, FileSystemEntity.type(target))) .then((_) => FutureExpect.equals(FileSystemEntityType.link, FileSystemEntity.type(link, followLinks: false))) .then((_) => FutureExpect.equals(FileSystemEntityType.directory, FileSystemEntity.type(target, followLinks: false))) .then((_) => FutureExpect.isTrue(FileSystemEntity.isLink(link))) .then((_) => FutureExpect.isFalse(FileSystemEntity.isLink(target))) .then((_) => FutureExpect.isTrue(new Directory(link).exists())) .then((_) => FutureExpect.isTrue(new Directory(target).exists())) .then((_) => FutureExpect.isTrue(new Link(link).exists())) .then((_) => FutureExpect.isFalse(new Link(target).exists())) .then((_) => FutureExpect.equals(target, new Link(link).target())) .then((_) => FutureExpect.throws(new Link(target).target())) .then((_) { String createdThroughLink = join(base, 'link', 'createdThroughLink'); String createdDirectly = join(base, 'target', 'createdDirectly'); String createdFile = join(base, 'link', 'createdFile'); return new Directory(createdThroughLink) .create() .then((_) => new Directory(createdDirectly).create()) .then((_) => new File(createdFile).create()) .then((_) => FutureExpect.isTrue(new Directory(createdThroughLink).exists())) .then((_) => FutureExpect.isTrue(new Directory(createdDirectly).exists())) .then((_) => FutureExpect.isTrue( new Directory(join(base, 'link', 'createdDirectly')).exists())) .then((_) => FutureExpect.isTrue( new Directory(join(base, 'target', 'createdThroughLink')) .exists())) .then((_) => FutureExpect.equals(FileSystemEntityType.directory, FileSystemEntity.type(createdThroughLink, followLinks: false))) .then((_) => FutureExpect.equals(FileSystemEntityType.directory, FileSystemEntity.type(createdDirectly, followLinks: false))) // Test FileSystemEntity.identical on files, directories, and links, // reached by different paths. .then( (_) => FutureExpect.isTrue(FileSystemEntity.identical(createdDirectly, createdDirectly))) .then((_) => FutureExpect.isFalse(FileSystemEntity.identical(createdDirectly, createdThroughLink))) .then((_) => FutureExpect.isTrue(FileSystemEntity.identical(createdDirectly, join(base, 'link', 'createdDirectly')))) .then((_) => FutureExpect.isTrue(FileSystemEntity.identical(createdThroughLink, join(base, 'target', 'createdThroughLink')))) .then((_) => FutureExpect.isFalse(FileSystemEntity.identical(target, link))) .then((_) => FutureExpect.isTrue(FileSystemEntity.identical(link, link))) .then((_) => FutureExpect.isTrue(FileSystemEntity.identical(target, target))) .then((_) => new Link(link).target()) .then((linkTarget) => FutureExpect.isTrue(FileSystemEntity.identical(target, linkTarget))) .then((_) => new File(".").resolveSymbolicLinks()) .then((fullCurrentDir) => FutureExpect.isTrue(FileSystemEntity.identical(".", fullCurrentDir))) .then((_) => FutureExpect.isTrue(FileSystemEntity.identical(createdFile, createdFile))) .then((_) => FutureExpect.isFalse(FileSystemEntity.identical(createdFile, createdDirectly))) .then((_) => FutureExpect.isTrue(FileSystemEntity.identical(createdFile, join(base, 'link', 'createdFile')))) .then((_) => FutureExpect.throws(FileSystemEntity.identical(createdFile, join(base, 'link', 'does_not_exist')))) .then((_) => testDirectoryListing(base, baseDir)) .then((_) => new Directory(target).delete(recursive: true)) .then((_) { var futures = []; for (bool recursive in [true, false]) { for (bool followLinks in [true, false]) { var result = baseDir.listSync( recursive: recursive, followLinks: followLinks); Expect.equals(1, result.length); Expect.isTrue(result[0] is Link); futures.add(FutureExpect.isTrue(baseDir .list(recursive: recursive, followLinks: followLinks) .single .then((element) => element is Link))); } } return Future.wait(futures); }).then((_) => baseDir.delete(recursive: true)); }); }); } Future testCreateLoopingLink(_) { return Directory.systemTemp .createTemp('dart_link_async') .then((dir) => dir.path) .then((String base) => new Directory(join(base, 'a', 'b', 'c')) .create(recursive: true) .then((_) => new Link(join(base, 'a', 'b', 'c', 'd')) .create(join(base, 'a', 'b'))) .then((_) => new Link(join(base, 'a', 'b', 'c', 'e')).create(join(base, 'a'))) .then((_) => new Directory(join(base, 'a')) .list(recursive: true, followLinks: false) .last) // This directory listing must terminate, even though it contains loops. .then((_) => new Directory(join(base, 'a')) .list(recursive: true, followLinks: true) .last) // This directory listing must terminate, even though it contains loops. .then((_) => new Directory(join(base, 'a', 'b', 'c')) .list(recursive: true, followLinks: true) .last) .then((_) => new Directory(base).delete(recursive: true)) .then((_) => FutureExpect.isFalse(new Directory(base).exists()))); } Future testRename(_) { Future testRename(String base, String target) { late Link link1; late Link link2; return new Link(join(base, 'c')).create(target).then((link) { link1 = link; Expect.isTrue(link1.existsSync()); return link1.rename(join(base, 'd')); }).then((link) { link2 = link; Expect.isFalse(link1.existsSync()); Expect.isTrue(link2.existsSync()); return link2.delete(); }).then((_) => Expect.isFalse(link2.existsSync())); } Future testUpdate(String base, String target1, String target2) { late Link link1; return new Link(join(base, 'c')).create(target1).then((link) { link1 = link; Expect.isTrue(link1.existsSync()); return link1.update(target2); }).then((Link link) { Expect.isTrue(link1.existsSync()); Expect.isTrue(link.existsSync()); return FutureExpect.equals(target2, link.target()) .then((_) => FutureExpect.equals(target2, link1.target())) .then((_) => link.delete()); }).then((_) => Expect.isFalse(link1.existsSync())); } return Directory.systemTemp.createTemp('dart_link_async').then((baseDir) { String base = baseDir.path; var targetsFutures = []; targetsFutures.add(new Directory(join(base, 'a')).create()); if (Platform.isWindows) { // Currently only links to directories are supported on Windows. targetsFutures.add(new Directory(join(base, 'b')).create()); } else { targetsFutures.add(new File(join(base, 'b')).create()); } return Future.wait(targetsFutures).then((targets) { return testRename(base, targets[0].path) .then((_) => testRename(base, targets[1].path)) .then((_) => testUpdate(base, targets[0].path, targets[1].path)) .then((_) => baseDir.delete(recursive: true)); }); }); } Future testDirectoryListing(String base, Directory baseDir) { Map makeExpected(bool recursive, bool followLinks) { Map expected = new Map(); expected['target'] = 'Directory'; expected['link'] = followLinks ? 'Directory' : 'Link'; if (recursive) { expected[join('target', 'createdDirectly')] = 'Directory'; expected[join('target', 'createdThroughLink')] = 'Directory'; expected[join('target', 'createdFile')] = 'File'; if (followLinks) { expected[join('link', 'createdDirectly')] = 'Directory'; expected[join('link', 'createdThroughLink')] = 'Directory'; expected[join('link', 'createdFile')] = 'File'; } } return expected; } void checkEntity(FileSystemEntity x, Map expected) { String ending = relative(x.path, from: base); Expect.isNotNull(expected[ending]); Expect.isTrue(x.toString().startsWith(expected[ending])); expected[ending] = 'Found'; } var futures = []; for (bool recursive in [true, false]) { for (bool followLinks in [true, false]) { Map expected = makeExpected(recursive, followLinks); for (var x in baseDir.listSync(recursive: recursive, followLinks: followLinks)) { checkEntity(x, expected); } for (var v in expected.values) { Expect.equals('Found', v); } expected = makeExpected(recursive, followLinks); futures.add(baseDir .list(recursive: recursive, followLinks: followLinks) .forEach((entity) => checkEntity(entity, expected)) .then((_) { for (var v in expected.values) { Expect.equals('Found', v); } })); } } return Future.wait(futures); } Future checkExists(String filePath) => new File(filePath).exists().then(Expect.isTrue); Future testRelativeLinks(_) { return Directory.systemTemp .createTemp('dart_link_async') .then((tempDirectory) { String temp = tempDirectory.path; String oldWorkingDirectory = Directory.current.path; // Make directories and files to test links. return new Directory(join(temp, 'dir1', 'dir2')) .create(recursive: true) .then((_) => new File(join(temp, 'dir1', 'file1')).create()) .then((_) => new File(join(temp, 'dir1', 'dir2', 'file2')).create()) // Make links whose path and/or target is given by a relative path. .then((_) => new Link(join(temp, 'dir1', 'link1_2')).create('dir2')) .then((_) => Directory.current = temp) .then((_) => new Link('link0_2').create(join('dir1', 'dir2'))) .then((_) => new Link(join('dir1', 'link1_0')).create('..')) .then((_) => Directory.current = 'dir1') .then((_) => new Link(join('..', 'link0_1')).create('dir1')) .then( (_) => new Link(join('dir2', 'link2_1')).create(join(temp, 'dir1'))) .then((_) => new Link(join(temp, 'dir1', 'dir2', 'link2_0')) .create(join('..', '..'))) // Test that the links go to the right targets. .then((_) => checkExists(join('..', 'link0_1', 'file1'))) .then((_) => checkExists(join('..', 'link0_2', 'file2'))) .then((_) => checkExists(join('link1_0', 'dir1', 'file1'))) .then((_) => checkExists(join('link1_2', 'file2'))) .then((_) => checkExists(join('dir2', 'link2_0', 'dir1', 'file1'))) .then((_) => checkExists(join('dir2', 'link2_1', 'file1'))) // Clean up .whenComplete(() => Directory.current = oldWorkingDirectory) .whenComplete(() => tempDirectory.delete(recursive: true)); }); } main() { asyncStart(); testCreate() .then(testCreateLoopingLink) .then(testRename) .then(testRelativeLinks) .then((_) => asyncEnd()); }