blob: bf82bf310d8134915ceb1559fa90ca5dfa0a9735 [file] [log] [blame]
// Copyright (c) 2020, 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.
//
// This test is Windows-only.
import 'dart:io';
import 'package:expect/expect.dart';
import 'package:path/path.dart' as p;
const maxPath = 260;
const maxDirectoryPath = maxPath - 12;
String longName = '${'x' * 248}';
Directory createLongPathDir(Directory tmp, [String? suffix]) {
var path = tmp.path;
path = p.join(path, 't' * 248);
var dir = Directory(path);
dir.createSync(recursive: true);
Expect.isTrue(dir.existsSync());
// Test the rename() of directory
dir = dir.renameSync(p.join(tmp.path, '$longName$suffix'));
Expect.isTrue(dir.existsSync());
Expect.isFalse(Directory(path).existsSync());
return dir;
}
void testCreate(String dir) {
final path = p.join(dir, 'a_long_path_filename');
Expect.isTrue(path.length > maxPath);
final file = File(path);
file.createSync();
Expect.isTrue(file.existsSync());
file.deleteSync();
}
void testCopy(String dir) {
final src = p.join(dir, 'a_long_path_filename_1');
final dest = p.join(dir, 'a_long_path_filename_2');
Expect.isTrue(src.length > maxPath);
final file1 = File(src);
file1.createSync();
final file2 = file1.copySync(dest);
Expect.isTrue(file2.existsSync());
file1.deleteSync();
file2.deleteSync();
}
void testRename(String dir) {
final path = p.join(dir, 'a_long_path_filename');
Expect.isTrue(path.length > maxPath);
final file = File(path);
file.createSync();
Expect.isTrue(file.existsSync());
final renamedFile = file.renameSync('${path}_copy');
Expect.isFalse(file.existsSync());
Expect.isTrue(renamedFile.existsSync());
renamedFile.deleteSync();
}
void testReadWrite(String dir) {
final path = p.join(dir, 'a_long_path_filename');
Expect.isTrue(path.length > maxPath);
final file = File(path);
final content = "testReadWrite";
file.writeAsStringSync(content);
Expect.isTrue(file.existsSync());
int length = file.lengthSync();
Expect.equals(content.length, length);
final string = file.readAsStringSync();
Expect.equals(content, string);
file.deleteSync();
}
void testOpen(String dir) {
final path = p.join(dir, 'a_long_path_filename');
Expect.isTrue(path.length > maxPath);
final file = File(path);
file.createSync();
final access = file.openSync();
access.closeSync();
}
void testFileStat(String dir) {
final path = p.join(dir, 'a_long_path_filename');
Expect.isTrue(path.length > maxPath);
final file = File(path);
file.createSync();
final stat = FileStat.statSync(file.path);
final dateTime = DateTime.utc(2020);
file.setLastModifiedSync(dateTime);
Expect.notEquals(
stat.modified.toString(), file.lastModifiedSync().toString());
file.setLastAccessedSync(dateTime);
Expect.notEquals(
stat.accessed.toString(), file.lastAccessedSync().toString());
}
String _createDirectoryHelper(String currentDir, String targetDir) {
if (p.isRelative(targetDir)) {
targetDir = p.normalize(p.absolute(currentDir, targetDir));
}
final dir = Directory(targetDir)..createSync();
Expect.isTrue(dir.existsSync());
return dir.path;
}
void testCreateLinkToDir(String dir, String dir2) {
final linkPath = p.join(dir, 'a_long_path_linkname');
final renamedPath = p.join(dir, 'a_long_renamed_path_linkname');
Expect.isTrue(linkPath.length > maxPath);
Expect.isTrue(renamedPath.length > maxPath);
final targetDirectory1 =
_createDirectoryHelper(dir, p.join(dir2, 'a_long_directory_target1'));
final targetDirectory2 =
_createDirectoryHelper(dir, p.join(dir2, 'a_long_directory_target2'));
final linkTarget1 = p.isRelative(dir2)
? p.relative(targetDirectory1, from: p.dirname(p.absolute(linkPath)))
: targetDirectory1;
final linkTarget2 = p.isRelative(dir2)
? p.relative(targetDirectory2, from: p.dirname(p.absolute(linkPath)))
: targetDirectory2;
// Create link
final link = Link(linkPath)..createSync(linkTarget1);
Expect.isTrue(link.existsSync());
final resolvedCreatePath = link.resolveSymbolicLinksSync();
Expect.isTrue(
FileSystemEntity.identicalSync(targetDirectory1, resolvedCreatePath),
'${link.path} should resolve to $targetDirectory1 but resolved to $resolvedCreatePath');
// Rename link
var renamedLink = link.renameSync(renamedPath);
Expect.isTrue(renamedLink.existsSync());
Expect.isFalse(link.existsSync());
final resolvedRenamePath = renamedLink.resolveSymbolicLinksSync();
Expect.isTrue(
FileSystemEntity.identicalSync(targetDirectory1, resolvedRenamePath),
'${link.path} should resolve to $targetDirectory1 but resolved to $resolvedRenamePath');
// Update link target
renamedLink.updateSync(linkTarget2);
final resolvedUpdatedPath = renamedLink.resolveSymbolicLinksSync();
Expect.isTrue(
FileSystemEntity.identicalSync(targetDirectory2, resolvedUpdatedPath),
'${link.path} should resolve to $targetDirectory2 but resolved to $resolvedRenamePath');
Directory(targetDirectory1).deleteSync();
Directory(targetDirectory2).deleteSync();
renamedLink.deleteSync();
}
void testCreateLinkToFile(String dir, String dir2) {
final path = p.join(dir, 'a_long_path_linkname');
Expect.isTrue(path.length > maxPath);
String _pathHelper(String currentDir, String targetPath) {
if (p.isRelative(targetPath)) {
return p.normalize(p.absolute(currentDir, targetPath));
} else {
return targetPath;
}
}
var target = _pathHelper(dir, p.join(dir2, 'a_long_path_target'));
var target2 = _pathHelper(dir, p.join(dir2, 'an_updated_target'));
var linkTarget = p.isRelative(dir2)
? p.relative(target, from: p.dirname(p.absolute(path)))
: target;
var linkTarget2 = p.isRelative(dir2)
? p.relative(target2, from: p.dirname(p.absolute(path)))
: target2;
final link = Link(path)..createSync(linkTarget);
final dest = File(target)..createSync();
Expect.isTrue(dest.existsSync());
Expect.isTrue(link.existsSync());
final resolvedPath = link.resolveSymbolicLinksSync();
Expect.isTrue(FileSystemEntity.identicalSync(target, resolvedPath),
'${link.path} should resolve to $target but resolved to $resolvedPath');
// Rename link
var renamedLink = link.renameSync(p.join(dir, 'a_renamed_long_path_link'));
Expect.isTrue(renamedLink.existsSync());
Expect.isFalse(link.existsSync());
Expect.isTrue(renamedLink.targetSync().contains('a_long_path_target'));
// Update link target
final renamedDest = File(target2)..createSync();
renamedLink.updateSync(linkTarget2);
Expect.isTrue(renamedLink.targetSync().contains('an_updated_target'));
dest.deleteSync();
renamedDest.deleteSync();
renamedLink.deleteSync();
}
testNormalLinkToLongPath(String short, String long) {
var target = File(p.join(long, 'file_target'))..createSync();
final link = Link(p.join(short, 'link'))..createSync(target.path);
Expect.isTrue(target.path.length > maxPath);
Expect.isTrue(link.resolveSymbolicLinksSync().length > maxPath);
Expect.isTrue(link.path.length < maxPath);
Expect.isTrue(link.resolveSymbolicLinksSync().contains('file_target'));
Expect.isTrue(link.existsSync());
Expect.equals(target.path, link.targetSync());
var targetDir = Directory(p.join(long, 'dir_target'))..createSync();
link.updateSync(targetDir.path);
Expect.equals(targetDir.path, link.targetSync());
link.deleteSync();
target.deleteSync();
targetDir.deleteSync();
}
testLongPathLinkToNormal(String short, String long) {
var target = File(p.join(short, 'file_target'))..createSync();
final link = Link(p.join(long, 'link'))..createSync(target.path);
Expect.isTrue(target.path.length < maxPath);
Expect.isTrue(link.path.length > maxPath);
Expect.isTrue(link.resolveSymbolicLinksSync().contains('file_target'));
Expect.isTrue(link.existsSync());
Expect.equals(target.path, link.targetSync());
var targetDir = Directory(p.join(short, 'dir_target'))..createSync();
link.updateSync(targetDir.path);
Expect.equals(targetDir.path, link.targetSync());
link.deleteSync();
target.deleteSync();
targetDir.deleteSync();
}
testDirectorySetCurrentFails(String dir) {
// This tests setting a long path directory to current directory.
Expect.isTrue(dir.length > maxPath);
if (Platform.isWindows) {
// On Windows current directory path is limited to MAX_PATH characters.
// Windows 10, Version 1607 introduced a way to lift this limitation but
// it requires opt-in from both application and the OS configuration.
//
// See https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#enable-long-paths-in-windows-10-version-1607-and-later
Expect.throws<FileSystemException>(() {
Directory.current = dir;
}, (e) => e.toString().contains('extension is too long'));
}
}
void main() {
final tmp = Directory.systemTemp.createTempSync('flpt');
final oldCurrent = Directory.current;
Directory.current = tmp;
try {
String dir1 = createLongPathDir(tmp, 'dir1').path;
String dir2 = createLongPathDir(tmp, 'dir2').path;
testDirectorySetCurrentFails(dir1);
for (final path in [
dir1,
p.join('.', '${longName}dir1'),
p.join('.', '${longName}dir1', '..', '${longName}dir1'),
]) {
testCreate(path);
testCopy(path);
testRename(path);
testReadWrite(path);
testOpen(path);
testFileStat(path);
for (var relative in [true, false]) {
final targetDir = relative ? p.relative(dir2, from: dir1) : dir2;
testCreateLinkToDir(path, targetDir);
testCreateLinkToFile(path, targetDir);
}
}
testNormalLinkToLongPath(tmp.path, dir1);
testLongPathLinkToNormal(tmp.path, dir1);
} finally {
// Reset the current Directory.
Directory.current = oldCurrent;
tmp.deleteSync(recursive: true);
}
}