bbfda6599a
Reviewed-by: gli, alanb
1295 lines
44 KiB
Java
1295 lines
44 KiB
Java
/*
|
|
* Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/* @test
|
|
* @bug 4313887 6838333 6917021 7006126 6950237 8006645 8073061 8201407 8264744
|
|
* 8267820
|
|
* @summary Unit test for java.nio.file.Files copy and move methods (use -Dseed=X to set PRNG seed)
|
|
* @library .. /test/lib
|
|
* @build jdk.test.lib.Platform jdk.test.lib.RandomFactory
|
|
* CopyAndMove PassThroughFileSystem
|
|
* @run main/othervm CopyAndMove
|
|
* @key randomness
|
|
*/
|
|
|
|
import java.io.*;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.file.*;
|
|
import static java.nio.file.Files.*;
|
|
import static java.nio.file.LinkOption.*;
|
|
import static java.nio.file.StandardCopyOption.*;
|
|
import java.nio.file.attribute.*;
|
|
import java.util.*;
|
|
import java.util.concurrent.TimeUnit;
|
|
import jdk.test.lib.Platform;
|
|
import jdk.test.lib.RandomFactory;
|
|
|
|
public class CopyAndMove {
|
|
private static final String FAT32_TYPE;
|
|
static {
|
|
if (Platform.isLinux())
|
|
FAT32_TYPE = "vfat";
|
|
else if (Platform.isOSX())
|
|
FAT32_TYPE = "msdos";
|
|
else if (Platform.isWindows())
|
|
FAT32_TYPE = "FAT32";
|
|
else
|
|
FAT32_TYPE = "unknown";
|
|
}
|
|
|
|
static final Random rand = RandomFactory.getRandom();
|
|
static boolean heads() { return rand.nextBoolean(); }
|
|
private static boolean testPosixAttributes = false;
|
|
private static boolean targetVolumeIsFAT32 = false;
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
Path dir1 = TestUtil.createTemporaryDirectory();
|
|
try {
|
|
|
|
// Same directory
|
|
FileStore fileStore1 = getFileStore(dir1);
|
|
printDirInfo("dir1", dir1, fileStore1);
|
|
testPosixAttributes = fileStore1.supportsFileAttributeView("posix");
|
|
testCopyFileToFile(dir1, dir1, TestUtil.supportsSymbolicLinks(dir1));
|
|
testMove(dir1, dir1, TestUtil.supportsSymbolicLinks(dir1));
|
|
|
|
// Use test.dir to define second directory if possible as it might
|
|
// be a different volume/file system and so improve test coverage.
|
|
String testDir = System.getProperty("test.dir", ".");
|
|
Path dir2 = TestUtil.createTemporaryDirectory(testDir);
|
|
FileStore fileStore2 = getFileStore(dir2);
|
|
targetVolumeIsFAT32 = fileStore2.type().equals(FAT32_TYPE);
|
|
|
|
printDirInfo("dir2", dir2, fileStore2);
|
|
|
|
// If different type (format) from dir1, re-do same directory tests
|
|
if (!fileStore1.type().equals(fileStore2.type())) {
|
|
try {
|
|
testPosixAttributes =
|
|
fileStore2.supportsFileAttributeView("posix");
|
|
testCopyFileToFile(dir2, dir2, TestUtil.supportsSymbolicLinks(dir2));
|
|
testMove(dir2, dir2, TestUtil.supportsSymbolicLinks(dir2));
|
|
} finally {
|
|
TestUtil.removeAll(dir2);
|
|
}
|
|
}
|
|
|
|
// Different directories.
|
|
try {
|
|
// Recreate dir2 if it was removed above
|
|
if (notExists(dir2)) {
|
|
dir2 = TestUtil.createTemporaryDirectory(testDir);
|
|
}
|
|
boolean testSymbolicLinks =
|
|
TestUtil.supportsSymbolicLinks(dir1) && TestUtil.supportsSymbolicLinks(dir2);
|
|
testPosixAttributes = fileStore1.supportsFileAttributeView("posix") &&
|
|
fileStore2.supportsFileAttributeView("posix");
|
|
testCopyFileToFile(dir1, dir2, testSymbolicLinks);
|
|
testMove(dir1, dir2, testSymbolicLinks);
|
|
} finally {
|
|
TestUtil.removeAll(dir2);
|
|
}
|
|
|
|
// Target is location associated with custom provider
|
|
Path dir3 = PassThroughFileSystem.create().getPath(dir1.toString());
|
|
FileStore fileStore3 = getFileStore(dir3);
|
|
targetVolumeIsFAT32 = false;
|
|
printDirInfo("dir3", dir3, fileStore3);
|
|
testPosixAttributes = fileStore1.supportsFileAttributeView("posix") &&
|
|
fileStore3.supportsFileAttributeView("posix");
|
|
testCopyFileToFile(dir1, dir3, false);
|
|
testMove(dir1, dir3, false);
|
|
|
|
// Test copy(InputStream,Path) and copy(Path,OutputStream)
|
|
testCopyInputStreamToFile();
|
|
testCopyFileToOuputStream();
|
|
|
|
} finally {
|
|
TestUtil.removeAll(dir1);
|
|
}
|
|
}
|
|
|
|
static void printDirInfo(String name, Path dir, FileStore store)
|
|
throws IOException {
|
|
System.err.format("%s: %s (%s)%n", name, dir, store.type());
|
|
}
|
|
|
|
static void checkBasicAttributes(BasicFileAttributes attrs1,
|
|
BasicFileAttributes attrs2)
|
|
{
|
|
// check file type
|
|
assertTrue(attrs1.isRegularFile() == attrs2.isRegularFile());
|
|
assertTrue(attrs1.isDirectory() == attrs2.isDirectory());
|
|
assertTrue(attrs1.isSymbolicLink() == attrs2.isSymbolicLink());
|
|
assertTrue(attrs1.isOther() == attrs2.isOther());
|
|
|
|
// check last modified time if not a symbolic link
|
|
if (!attrs1.isSymbolicLink()) {
|
|
long time1 = attrs1.lastModifiedTime().to(TimeUnit.SECONDS);
|
|
long time2 = attrs2.lastModifiedTime().to(TimeUnit.SECONDS);
|
|
long delta = Math.abs(Math.subtractExact(time1, time2));
|
|
|
|
// FAT32 volumes have a time stamp resolution of 2 seconds for
|
|
// last modified time (write time)
|
|
if ((delta != 0 && !targetVolumeIsFAT32) || delta > 2) {
|
|
System.err.format("File time for %s is %s\n",
|
|
attrs1.fileKey(), attrs1.lastModifiedTime());
|
|
System.err.format("File time for %s is %s\n",
|
|
attrs2.fileKey(), attrs2.lastModifiedTime());
|
|
assertTrue(false);
|
|
}
|
|
}
|
|
|
|
// check size
|
|
if (attrs1.isRegularFile())
|
|
assertTrue(attrs1.size() == attrs2.size());
|
|
}
|
|
|
|
static void checkPosixAttributes(PosixFileAttributes attrs1,
|
|
PosixFileAttributes attrs2)
|
|
{
|
|
assertTrue(attrs1.permissions().equals(attrs2.permissions()),
|
|
"permissions%n1 (%d): %s%n2 (%d): %s%n%n",
|
|
attrs1.permissions().size(), attrs1.permissions(),
|
|
attrs2.permissions().size(), attrs2.permissions());
|
|
assertTrue(attrs1.owner().equals(attrs2.owner()),
|
|
"owner%n1: %s%n2: %s%n%n", attrs1.owner(), attrs2.owner());
|
|
assertTrue(attrs1.group().equals(attrs2.group()),
|
|
"group%n1: %s%n2: %s%n%n", attrs1.group(), attrs2.group());
|
|
}
|
|
|
|
static void checkDosAttributes(DosFileAttributes attrs1,
|
|
DosFileAttributes attrs2)
|
|
{
|
|
assertTrue(attrs1.isReadOnly() == attrs2.isReadOnly(),
|
|
"isReadOnly%n1: %s%n2: %s%n%n", attrs1.isReadOnly(), attrs2.isReadOnly());
|
|
assertTrue(attrs1.isHidden() == attrs2.isHidden(),
|
|
"isHidden%n1: %s%n2: %s%n%n", attrs1.isHidden(), attrs2.isHidden());
|
|
assertTrue(attrs1.isSystem() == attrs2.isSystem(),
|
|
"isSystem%n1: %s%n2: %s%n%n", attrs1.isSystem(), attrs2.isSystem());
|
|
}
|
|
|
|
static void checkUserDefinedFileAttributes(Map<String,ByteBuffer> attrs1,
|
|
Map<String,ByteBuffer> attrs2)
|
|
{
|
|
assert attrs1.size() == attrs2.size();
|
|
for (String name: attrs1.keySet()) {
|
|
ByteBuffer bb1 = attrs1.get(name);
|
|
ByteBuffer bb2 = attrs2.get(name);
|
|
assertTrue(bb2 != null);
|
|
assertTrue(bb1.equals(bb2));
|
|
}
|
|
}
|
|
|
|
static Map<String,ByteBuffer> readUserDefinedFileAttributes(Path file)
|
|
throws IOException
|
|
{
|
|
UserDefinedFileAttributeView view =
|
|
getFileAttributeView(file, UserDefinedFileAttributeView.class);
|
|
Map<String,ByteBuffer> result = new HashMap<>();
|
|
for (String name: view.list()) {
|
|
int size = view.size(name);
|
|
ByteBuffer bb = ByteBuffer.allocate(size);
|
|
int n = view.read(name, bb);
|
|
assertTrue(n == size);
|
|
bb.flip();
|
|
result.put(name, bb);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// move source to target with verification
|
|
static void moveAndVerify(Path source, Path target, CopyOption... options)
|
|
throws IOException
|
|
{
|
|
// read attributes before file is moved
|
|
BasicFileAttributes basicAttributes = null;
|
|
PosixFileAttributes posixAttributes = null;
|
|
DosFileAttributes dosAttributes = null;
|
|
Map<String,ByteBuffer> namedAttributes = null;
|
|
|
|
// get file attributes of source file
|
|
if (Platform.isWindows()) {
|
|
dosAttributes = readAttributes(source, DosFileAttributes.class, NOFOLLOW_LINKS);
|
|
basicAttributes = dosAttributes;
|
|
} else {
|
|
posixAttributes = readAttributes(source, PosixFileAttributes.class, NOFOLLOW_LINKS);
|
|
basicAttributes = posixAttributes;
|
|
}
|
|
if (basicAttributes == null)
|
|
basicAttributes = readAttributes(source, BasicFileAttributes.class, NOFOLLOW_LINKS);
|
|
|
|
// hash file contents if regular file
|
|
int hash = (basicAttributes.isRegularFile()) ? computeHash(source) : 0;
|
|
|
|
// record link target if symbolic link
|
|
Path linkTarget = null;
|
|
if (basicAttributes.isSymbolicLink())
|
|
linkTarget = readSymbolicLink(source);
|
|
|
|
// read named attributes if available (and file is not a sym link)
|
|
if (!basicAttributes.isSymbolicLink() &&
|
|
getFileStore(source).supportsFileAttributeView("xattr"))
|
|
{
|
|
namedAttributes = readUserDefinedFileAttributes(source);
|
|
}
|
|
|
|
// move file
|
|
Path result = move(source, target, options);
|
|
assertTrue(result == target);
|
|
|
|
// verify source does not exist
|
|
assertTrue(notExists(source));
|
|
|
|
// verify file contents
|
|
if (basicAttributes.isRegularFile()) {
|
|
if (computeHash(target) != hash)
|
|
throw new RuntimeException("Failed to verify move of regular file");
|
|
}
|
|
|
|
// verify link target
|
|
if (basicAttributes.isSymbolicLink()) {
|
|
if (!readSymbolicLink(target).equals(linkTarget))
|
|
throw new RuntimeException("Failed to verify move of symbolic link");
|
|
}
|
|
|
|
// verify basic attributes
|
|
checkBasicAttributes(basicAttributes,
|
|
readAttributes(target, BasicFileAttributes.class, NOFOLLOW_LINKS));
|
|
|
|
// verify other attributes when same provider
|
|
if (source.getFileSystem().provider() == target.getFileSystem().provider()) {
|
|
|
|
// verify POSIX attributes
|
|
if (posixAttributes != null &&
|
|
!basicAttributes.isSymbolicLink() &&
|
|
testPosixAttributes)
|
|
{
|
|
checkPosixAttributes(posixAttributes,
|
|
readAttributes(target, PosixFileAttributes.class, NOFOLLOW_LINKS));
|
|
}
|
|
|
|
// verify DOS attributes
|
|
if (dosAttributes != null && !basicAttributes.isSymbolicLink()) {
|
|
DosFileAttributes attrs =
|
|
readAttributes(target, DosFileAttributes.class, NOFOLLOW_LINKS);
|
|
checkDosAttributes(dosAttributes, attrs);
|
|
}
|
|
|
|
// verify named attributes
|
|
if (namedAttributes != null &&
|
|
getFileStore(target).supportsFileAttributeView("xattr"))
|
|
{
|
|
checkUserDefinedFileAttributes(namedAttributes,
|
|
readUserDefinedFileAttributes(target));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tests all possible ways to invoke move
|
|
*/
|
|
static void testMove(Path dir1, Path dir2, boolean supportsSymbolicLinks)
|
|
throws IOException
|
|
{
|
|
Path source, target, entry;
|
|
|
|
boolean sameDevice = getFileStore(dir1).equals(getFileStore(dir2));
|
|
|
|
// -- regular file --
|
|
|
|
/**
|
|
* Test: move regular file, target does not exist
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
moveAndVerify(source, target);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: move regular file, target exists
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
createFile(target);
|
|
try {
|
|
moveAndVerify(source, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException x) {
|
|
}
|
|
delete(target);
|
|
createDirectory(target);
|
|
try {
|
|
moveAndVerify(source, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException x) {
|
|
}
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: move regular file, target does not exist
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
moveAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: move regular file, target exists
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
createFile(target);
|
|
moveAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: move regular file, target exists and is empty directory
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
createDirectory(target);
|
|
moveAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: move regular file, target exists and is non-empty directory
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
createDirectory(target);
|
|
entry = target.resolve("foo");
|
|
createFile(entry);
|
|
try {
|
|
moveAndVerify(source, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException x) {
|
|
}
|
|
delete(entry);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test atomic move of regular file (same file store)
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir1);
|
|
moveAndVerify(source, target, ATOMIC_MOVE);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test atomic move of regular file (different file store)
|
|
*/
|
|
if (!sameDevice) {
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
try {
|
|
moveAndVerify(source, target, ATOMIC_MOVE);
|
|
throw new RuntimeException("AtomicMoveNotSupportedException expected");
|
|
} catch (AtomicMoveNotSupportedException x) {
|
|
}
|
|
delete(source);
|
|
}
|
|
|
|
// -- directories --
|
|
|
|
/*
|
|
* Test: move empty directory, target does not exist
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
moveAndVerify(source, target);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: move empty directory, target exists
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
createFile(target);
|
|
try {
|
|
moveAndVerify(source, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException x) {
|
|
}
|
|
delete(target);
|
|
createDirectory(target);
|
|
try {
|
|
moveAndVerify(source, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException x) {
|
|
}
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: move empty directory, target does not exist
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
moveAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: move empty directory, target exists
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
createFile(target);
|
|
moveAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: move empty, target exists and is empty directory
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
createDirectory(target);
|
|
moveAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: move empty directory, target exists and is non-empty directory
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
createDirectory(target);
|
|
entry = target.resolve("foo");
|
|
createFile(entry);
|
|
try {
|
|
moveAndVerify(source, target, REPLACE_EXISTING);
|
|
throw new RuntimeException("DirectoryNotEmptyException expected");
|
|
} catch (DirectoryNotEmptyException x) {
|
|
}
|
|
delete(entry);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: move non-empty directory (same file system)
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
createFile(source.resolve("foo"));
|
|
target = getTargetFile(dir1);
|
|
moveAndVerify(source, target);
|
|
delete(target.resolve("foo"));
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: move non-empty directory (different file store)
|
|
*/
|
|
if (!sameDevice) {
|
|
source = createSourceDirectory(dir1);
|
|
createFile(source.resolve("foo"));
|
|
target = getTargetFile(dir2);
|
|
try {
|
|
moveAndVerify(source, target);
|
|
throw new RuntimeException("IOException expected");
|
|
} catch (IOException x) {
|
|
if (!(x instanceof DirectoryNotEmptyException)) {
|
|
throw new RuntimeException
|
|
("DirectoryNotEmptyException expected", x);
|
|
}
|
|
}
|
|
delete(source.resolve("foo"));
|
|
delete(source);
|
|
}
|
|
|
|
/**
|
|
* Test atomic move of directory (same file store)
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
createFile(source.resolve("foo"));
|
|
target = getTargetFile(dir1);
|
|
moveAndVerify(source, target, ATOMIC_MOVE);
|
|
delete(target.resolve("foo"));
|
|
delete(target);
|
|
|
|
// -- symbolic links --
|
|
|
|
/**
|
|
* Test: Move symbolic link to file, target does not exist
|
|
*/
|
|
if (supportsSymbolicLinks) {
|
|
Path tmp = createSourceFile(dir1);
|
|
source = dir1.resolve("link");
|
|
createSymbolicLink(source, tmp);
|
|
target = getTargetFile(dir2);
|
|
moveAndVerify(source, target);
|
|
delete(target);
|
|
delete(tmp);
|
|
}
|
|
|
|
/**
|
|
* Test: Move symbolic link to directory, target does not exist
|
|
*/
|
|
if (supportsSymbolicLinks) {
|
|
source = dir1.resolve("link");
|
|
createSymbolicLink(source, dir2);
|
|
target = getTargetFile(dir2);
|
|
moveAndVerify(source, target);
|
|
delete(target);
|
|
}
|
|
|
|
/**
|
|
* Test: Move broken symbolic link, target does not exists
|
|
*/
|
|
if (supportsSymbolicLinks) {
|
|
Path tmp = Paths.get("doesnotexist");
|
|
source = dir1.resolve("link");
|
|
createSymbolicLink(source, tmp);
|
|
target = getTargetFile(dir2);
|
|
moveAndVerify(source, target);
|
|
delete(target);
|
|
}
|
|
|
|
/**
|
|
* Test: Move symbolic link, target exists
|
|
*/
|
|
if (supportsSymbolicLinks) {
|
|
source = dir1.resolve("link");
|
|
createSymbolicLink(source, dir2);
|
|
target = getTargetFile(dir2);
|
|
createFile(target);
|
|
try {
|
|
moveAndVerify(source, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException x) {
|
|
}
|
|
delete(source);
|
|
delete(target);
|
|
}
|
|
|
|
/**
|
|
* Test: Move regular file, target exists
|
|
*/
|
|
if (supportsSymbolicLinks) {
|
|
source = dir1.resolve("link");
|
|
createSymbolicLink(source, dir2);
|
|
target = getTargetFile(dir2);
|
|
createFile(target);
|
|
moveAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(target);
|
|
}
|
|
|
|
/**
|
|
* Test: move symbolic link, target exists and is empty directory
|
|
*/
|
|
if (supportsSymbolicLinks) {
|
|
source = dir1.resolve("link");
|
|
createSymbolicLink(source, dir2);
|
|
target = getTargetFile(dir2);
|
|
createDirectory(target);
|
|
moveAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(target);
|
|
}
|
|
|
|
/**
|
|
* Test: symbolic link, target exists and is non-empty directory
|
|
*/
|
|
if (supportsSymbolicLinks) {
|
|
source = dir1.resolve("link");
|
|
createSymbolicLink(source, dir2);
|
|
target = getTargetFile(dir2);
|
|
createDirectory(target);
|
|
entry = target.resolve("foo");
|
|
createFile(entry);
|
|
try {
|
|
moveAndVerify(source, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException x) {
|
|
}
|
|
delete(entry);
|
|
delete(source);
|
|
delete(target);
|
|
}
|
|
|
|
/**
|
|
* Test atomic move of symbolic link (same file store)
|
|
*/
|
|
if (supportsSymbolicLinks) {
|
|
source = dir1.resolve("link");
|
|
createSymbolicLink(source, dir1);
|
|
target = getTargetFile(dir2);
|
|
createFile(target);
|
|
moveAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(target);
|
|
}
|
|
|
|
// -- misc. tests --
|
|
|
|
/**
|
|
* Test nulls
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
try {
|
|
move(null, target);
|
|
throw new RuntimeException("NullPointerException expected");
|
|
} catch (NullPointerException x) { }
|
|
try {
|
|
move(source, null);
|
|
throw new RuntimeException("NullPointerException expected");
|
|
} catch (NullPointerException x) { }
|
|
try {
|
|
move(source, target, (CopyOption[])null);
|
|
throw new RuntimeException("NullPointerException expected");
|
|
} catch (NullPointerException x) { }
|
|
try {
|
|
CopyOption[] opts = { REPLACE_EXISTING, null };
|
|
move(source, target, opts);
|
|
throw new RuntimeException("NullPointerException expected");
|
|
} catch (NullPointerException x) { }
|
|
delete(source);
|
|
|
|
/**
|
|
* Test UOE
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
try {
|
|
move(source, target, new CopyOption() { });
|
|
} catch (UnsupportedOperationException x) { }
|
|
try {
|
|
move(source, target, REPLACE_EXISTING, new CopyOption() { });
|
|
} catch (UnsupportedOperationException x) { }
|
|
delete(source);
|
|
}
|
|
|
|
// copy source to target with verification
|
|
static void copyAndVerify(Path source, Path target, CopyOption... options)
|
|
throws IOException
|
|
{
|
|
Path result = copy(source, target, options);
|
|
assertTrue(result == target);
|
|
|
|
// get attributes of source and target file to verify copy
|
|
boolean followLinks = true;
|
|
LinkOption[] linkOptions = new LinkOption[0];
|
|
boolean copyAttributes = false;
|
|
for (CopyOption opt : options) {
|
|
if (opt == NOFOLLOW_LINKS) {
|
|
followLinks = false;
|
|
linkOptions = new LinkOption[] { NOFOLLOW_LINKS };
|
|
}
|
|
if (opt == COPY_ATTRIBUTES)
|
|
copyAttributes = true;
|
|
}
|
|
BasicFileAttributes basicAttributes =
|
|
readAttributes(source, BasicFileAttributes.class, linkOptions);
|
|
|
|
// check hash if regular file
|
|
if (basicAttributes.isRegularFile())
|
|
assertTrue(computeHash(source) == computeHash(target));
|
|
|
|
// check link target if symbolic link
|
|
if (basicAttributes.isSymbolicLink())
|
|
assert(readSymbolicLink(source).equals(readSymbolicLink(target)));
|
|
|
|
// check that attributes are copied
|
|
if (copyAttributes && followLinks) {
|
|
checkBasicAttributes(basicAttributes,
|
|
readAttributes(source, BasicFileAttributes.class, linkOptions));
|
|
|
|
// check POSIX attributes are copied
|
|
if (!Platform.isWindows() && testPosixAttributes) {
|
|
checkPosixAttributes(
|
|
readAttributes(source, PosixFileAttributes.class, linkOptions),
|
|
readAttributes(target, PosixFileAttributes.class, linkOptions));
|
|
}
|
|
|
|
// verify other attributes when same provider
|
|
if (source.getFileSystem().provider() == target.getFileSystem().provider()) {
|
|
// check DOS attributes are copied
|
|
if (Platform.isWindows()) {
|
|
checkDosAttributes(
|
|
readAttributes(source, DosFileAttributes.class, linkOptions),
|
|
readAttributes(target, DosFileAttributes.class, linkOptions));
|
|
}
|
|
|
|
// check named attributes are copied
|
|
if (followLinks &&
|
|
getFileStore(source).supportsFileAttributeView("xattr") &&
|
|
getFileStore(target).supportsFileAttributeView("xattr"))
|
|
{
|
|
checkUserDefinedFileAttributes(readUserDefinedFileAttributes(source),
|
|
readUserDefinedFileAttributes(target));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tests all possible ways to invoke copy to copy a file to a file
|
|
*/
|
|
static void testCopyFileToFile(Path dir1, Path dir2, boolean supportsSymbolicLinks)
|
|
throws IOException
|
|
{
|
|
Path source, target, link, entry;
|
|
|
|
// -- regular file --
|
|
|
|
/**
|
|
* Test: move regular file, target does not exist
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
copyAndVerify(source, target);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: copy regular file, target exists
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
createFile(target);
|
|
try {
|
|
copyAndVerify(source, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException x) {
|
|
}
|
|
delete(target);
|
|
createDirectory(target);
|
|
try {
|
|
copyAndVerify(source, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException x) {
|
|
}
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: copy regular file, target does not exist
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
copyAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: copy regular file, target exists
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
createFile(target);
|
|
copyAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: copy regular file, target exists and is empty directory
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
createDirectory(target);
|
|
copyAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: copy regular file, target exists and is non-empty directory
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
createDirectory(target);
|
|
entry = target.resolve("foo");
|
|
createFile(entry);
|
|
try {
|
|
copyAndVerify(source, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException x) {
|
|
}
|
|
delete(entry);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: copy regular file + attributes
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
copyAndVerify(source, target, COPY_ATTRIBUTES);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
|
|
/**
|
|
* Test: ensure target not deleted if source permissions are zero
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
if (getFileStore(source).supportsFileAttributeView("posix")) {
|
|
Files.setPosixFilePermissions(source, Set.of());
|
|
target = getTargetFile(dir2);
|
|
createFile(target);
|
|
try {
|
|
Files.copy(source, target, REPLACE_EXISTING);
|
|
throw new RuntimeException("AccessDeniedException not thrown");
|
|
} catch (AccessDeniedException expected) {
|
|
}
|
|
if (!Files.exists(target))
|
|
throw new RuntimeException("target deleted");
|
|
delete(target);
|
|
}
|
|
delete(source);
|
|
|
|
// -- directory --
|
|
|
|
/*
|
|
* Test: copy directory, target does not exist
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
copyAndVerify(source, target);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: copy directory, target exists
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
createFile(target);
|
|
try {
|
|
copyAndVerify(source, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException x) {
|
|
}
|
|
delete(target);
|
|
createDirectory(target);
|
|
try {
|
|
copyAndVerify(source, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException x) {
|
|
}
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: copy directory, target does not exist
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
copyAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: copy directory, target exists
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
createFile(target);
|
|
copyAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: copy directory, target exists and is empty directory
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
createDirectory(target);
|
|
copyAndVerify(source, target, REPLACE_EXISTING);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/**
|
|
* Test: copy directory, target exists and is non-empty directory
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
createDirectory(target);
|
|
entry = target.resolve("foo");
|
|
createFile(entry);
|
|
try {
|
|
copyAndVerify(source, target, REPLACE_EXISTING);
|
|
throw new RuntimeException("DirectoryNotEmptyException expected");
|
|
} catch (DirectoryNotEmptyException x) {
|
|
}
|
|
delete(entry);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
/*
|
|
* Test: copy directory + attributes
|
|
*/
|
|
source = createSourceDirectory(dir1);
|
|
target = getTargetFile(dir2);
|
|
copyAndVerify(source, target, COPY_ATTRIBUTES);
|
|
delete(source);
|
|
delete(target);
|
|
|
|
// -- symbolic links --
|
|
|
|
/**
|
|
* Test: Follow link
|
|
*/
|
|
if (supportsSymbolicLinks) {
|
|
source = createSourceFile(dir1);
|
|
link = dir1.resolve("link");
|
|
createSymbolicLink(link, source.getFileName());
|
|
target = getTargetFile(dir2);
|
|
copyAndVerify(link, target);
|
|
delete(link);
|
|
delete(source);
|
|
}
|
|
|
|
/**
|
|
* Test: Copy link (to file)
|
|
*/
|
|
if (supportsSymbolicLinks) {
|
|
source = createSourceFile(dir1);
|
|
link = dir1.resolve("link");
|
|
createSymbolicLink(link, source);
|
|
target = getTargetFile(dir2);
|
|
copyAndVerify(link, target, NOFOLLOW_LINKS);
|
|
delete(link);
|
|
delete(source);
|
|
}
|
|
|
|
/**
|
|
* Test: Copy link (to directory)
|
|
*/
|
|
if (supportsSymbolicLinks) {
|
|
source = dir1.resolve("mydir");
|
|
createDirectory(source);
|
|
link = dir1.resolve("link");
|
|
createSymbolicLink(link, source);
|
|
target = getTargetFile(dir2);
|
|
copyAndVerify(link, target, NOFOLLOW_LINKS);
|
|
delete(link);
|
|
delete(source);
|
|
}
|
|
|
|
/**
|
|
* Test: Copy broken link
|
|
*/
|
|
if (supportsSymbolicLinks) {
|
|
assertTrue(notExists(source));
|
|
link = dir1.resolve("link");
|
|
createSymbolicLink(link, source);
|
|
target = getTargetFile(dir2);
|
|
copyAndVerify(link, target, NOFOLLOW_LINKS);
|
|
delete(link);
|
|
}
|
|
|
|
/**
|
|
* Test: Copy link to UNC (Windows only)
|
|
*/
|
|
if (supportsSymbolicLinks && Platform.isWindows()) {
|
|
Path unc = Paths.get("\\\\rialto\\share\\file");
|
|
link = dir1.resolve("link");
|
|
createSymbolicLink(link, unc);
|
|
target = getTargetFile(dir2);
|
|
copyAndVerify(link, target, NOFOLLOW_LINKS);
|
|
delete(link);
|
|
}
|
|
|
|
// -- misc. tests --
|
|
|
|
/**
|
|
* Test nulls
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
try {
|
|
copy(source, null);
|
|
throw new RuntimeException("NullPointerException expected");
|
|
} catch (NullPointerException x) { }
|
|
try {
|
|
copy(source, target, (CopyOption[])null);
|
|
throw new RuntimeException("NullPointerException expected");
|
|
} catch (NullPointerException x) { }
|
|
try {
|
|
CopyOption[] opts = { REPLACE_EXISTING, null };
|
|
copy(source, target, opts);
|
|
throw new RuntimeException("NullPointerException expected");
|
|
} catch (NullPointerException x) { }
|
|
delete(source);
|
|
|
|
/**
|
|
* Test UOE
|
|
*/
|
|
source = createSourceFile(dir1);
|
|
target = getTargetFile(dir2);
|
|
try {
|
|
copy(source, target, new CopyOption() { });
|
|
} catch (UnsupportedOperationException x) { }
|
|
try {
|
|
copy(source, target, REPLACE_EXISTING, new CopyOption() { });
|
|
} catch (UnsupportedOperationException x) { }
|
|
delete(source);
|
|
}
|
|
|
|
/**
|
|
* Test copy from an input stream to a file
|
|
*/
|
|
static void testCopyInputStreamToFile() throws IOException {
|
|
testCopyInputStreamToFile(0);
|
|
for (int i=0; i<100; i++) {
|
|
testCopyInputStreamToFile(rand.nextInt(32000));
|
|
}
|
|
|
|
// FileAlreadyExistsException
|
|
Path target = createTempFile("blah", null);
|
|
try {
|
|
InputStream in = new ByteArrayInputStream(new byte[0]);
|
|
try {
|
|
copy(in, target);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException ignore) { }
|
|
} finally {
|
|
delete(target);
|
|
}
|
|
Path tmpdir = createTempDirectory("blah");
|
|
try {
|
|
if (TestUtil.supportsSymbolicLinks(tmpdir)) {
|
|
Path link = createSymbolicLink(tmpdir.resolve("link"),
|
|
tmpdir.resolve("target"));
|
|
try {
|
|
InputStream in = new ByteArrayInputStream(new byte[0]);
|
|
try {
|
|
copy(in, link);
|
|
throw new RuntimeException("FileAlreadyExistsException expected");
|
|
} catch (FileAlreadyExistsException ignore) { }
|
|
} finally {
|
|
delete(link);
|
|
}
|
|
}
|
|
} finally {
|
|
delete(tmpdir);
|
|
}
|
|
|
|
|
|
// nulls
|
|
try {
|
|
copy((InputStream)null, target);
|
|
throw new RuntimeException("NullPointerException expected");
|
|
} catch (NullPointerException ignore) { }
|
|
try {
|
|
copy(new ByteArrayInputStream(new byte[0]), (Path)null);
|
|
throw new RuntimeException("NullPointerException expected");
|
|
} catch (NullPointerException ignore) { }
|
|
}
|
|
|
|
static void testCopyInputStreamToFile(int size) throws IOException {
|
|
Path tmpdir = createTempDirectory("blah");
|
|
Path source = tmpdir.resolve("source");
|
|
Path target = tmpdir.resolve("target");
|
|
try {
|
|
boolean testReplaceExisting = rand.nextBoolean();
|
|
|
|
// create source file
|
|
byte[] b = new byte[size];
|
|
rand.nextBytes(b);
|
|
write(source, b);
|
|
|
|
// target file might already exist
|
|
if (testReplaceExisting && rand.nextBoolean()) {
|
|
write(target, new byte[rand.nextInt(512)]);
|
|
}
|
|
|
|
// copy from stream to file
|
|
InputStream in = new FileInputStream(source.toFile());
|
|
try {
|
|
long n;
|
|
if (testReplaceExisting) {
|
|
n = copy(in, target, StandardCopyOption.REPLACE_EXISTING);
|
|
} else {
|
|
n = copy(in, target);
|
|
}
|
|
assertTrue(in.read() == -1); // EOF
|
|
assertTrue(n == size);
|
|
assertTrue(size(target) == size);
|
|
} finally {
|
|
in.close();
|
|
}
|
|
|
|
// check file
|
|
byte[] read = readAllBytes(target);
|
|
assertTrue(Arrays.equals(read, b));
|
|
|
|
} finally {
|
|
deleteIfExists(source);
|
|
deleteIfExists(target);
|
|
delete(tmpdir);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test copy from file to output stream
|
|
*/
|
|
static void testCopyFileToOuputStream() throws IOException {
|
|
testCopyFileToOuputStream(0);
|
|
for (int i=0; i<100; i++) {
|
|
testCopyFileToOuputStream(rand.nextInt(32000));
|
|
}
|
|
|
|
// nulls
|
|
try {
|
|
copy((Path)null, new ByteArrayOutputStream());
|
|
throw new RuntimeException("NullPointerException expected");
|
|
} catch (NullPointerException ignore) { }
|
|
try {
|
|
Path source = createTempFile("blah", null);
|
|
delete(source);
|
|
copy(source, (OutputStream)null);
|
|
throw new RuntimeException("NullPointerException expected");
|
|
} catch (NullPointerException ignore) { }
|
|
}
|
|
|
|
static void testCopyFileToOuputStream(int size) throws IOException {
|
|
Path source = createTempFile("blah", null);
|
|
try {
|
|
byte[] b = new byte[size];
|
|
rand.nextBytes(b);
|
|
write(source, b);
|
|
|
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
|
|
long n = copy(source, out);
|
|
assertTrue(n == size);
|
|
assertTrue(out.size() == size);
|
|
|
|
byte[] read = out.toByteArray();
|
|
assertTrue(Arrays.equals(read, b));
|
|
|
|
// check output stream is open
|
|
out.write(0);
|
|
assertTrue(out.size() == size+1);
|
|
} finally {
|
|
delete(source);
|
|
}
|
|
}
|
|
|
|
static void assertTrue(boolean value) {
|
|
if (!value)
|
|
throw new RuntimeException("Assertion failed");
|
|
}
|
|
|
|
static void assertTrue(boolean value, String format, Object... args) {
|
|
if (!value) {
|
|
System.err.format(format, args);
|
|
throw new RuntimeException("Assertion failed");
|
|
}
|
|
}
|
|
|
|
// computes simple hash of the given file
|
|
static int computeHash(Path file) throws IOException {
|
|
int h = 0;
|
|
|
|
try (InputStream in = newInputStream(file)) {
|
|
byte[] buf = new byte[1024];
|
|
int n;
|
|
do {
|
|
n = in.read(buf);
|
|
for (int i=0; i<n; i++) {
|
|
h = 31*h + (buf[i] & 0xff);
|
|
}
|
|
} while (n > 0);
|
|
}
|
|
return h;
|
|
}
|
|
|
|
// create file of random size in given directory
|
|
static Path createSourceFile(Path dir) throws IOException {
|
|
String name = "source" + Integer.toString(rand.nextInt());
|
|
Path file = dir.resolve(name);
|
|
createFile(file);
|
|
byte[] bytes = new byte[rand.nextInt(128*1024)];
|
|
rand.nextBytes(bytes);
|
|
try (OutputStream out = newOutputStream(file)) {
|
|
out.write(bytes);
|
|
}
|
|
randomizeAttributes(file);
|
|
return file;
|
|
}
|
|
|
|
// create directory in the given directory
|
|
static Path createSourceDirectory(Path dir) throws IOException {
|
|
String name = "sourcedir" + Integer.toString(rand.nextInt());
|
|
Path subdir = dir.resolve(name);
|
|
createDirectory(subdir);
|
|
randomizeAttributes(subdir);
|
|
return subdir;
|
|
}
|
|
|
|
// "randomize" the file attributes of the given file.
|
|
static void randomizeAttributes(Path file) throws IOException {
|
|
boolean isDirectory = isDirectory(file, NOFOLLOW_LINKS);
|
|
|
|
if (Platform.isWindows()) {
|
|
DosFileAttributeView view =
|
|
getFileAttributeView(file, DosFileAttributeView.class, NOFOLLOW_LINKS);
|
|
// only set or unset the hidden attribute
|
|
view.setHidden(heads());
|
|
} else {
|
|
Set<PosixFilePermission> perms =
|
|
getPosixFilePermissions(file, NOFOLLOW_LINKS);
|
|
PosixFilePermission[] toChange = {
|
|
PosixFilePermission.GROUP_READ,
|
|
PosixFilePermission.GROUP_WRITE,
|
|
PosixFilePermission.GROUP_EXECUTE,
|
|
PosixFilePermission.OTHERS_READ,
|
|
PosixFilePermission.OTHERS_WRITE,
|
|
PosixFilePermission.OTHERS_EXECUTE
|
|
};
|
|
for (PosixFilePermission perm: toChange) {
|
|
if (heads()) {
|
|
perms.add(perm);
|
|
} else {
|
|
perms.remove(perm);
|
|
}
|
|
}
|
|
setPosixFilePermissions(file, perms);
|
|
}
|
|
|
|
boolean addUserDefinedFileAttributes = heads() &&
|
|
getFileStore(file).supportsFileAttributeView("xattr");
|
|
|
|
// remove this when copying a direcory copies its named streams
|
|
if (Platform.isWindows() && isDirectory)
|
|
addUserDefinedFileAttributes = false;
|
|
|
|
if (addUserDefinedFileAttributes) {
|
|
UserDefinedFileAttributeView view =
|
|
getFileAttributeView(file, UserDefinedFileAttributeView.class);
|
|
int n = rand.nextInt(16);
|
|
while (n > 0) {
|
|
byte[] value = new byte[1 + rand.nextInt(100)];
|
|
view.write("user." + Integer.toString(n), ByteBuffer.wrap(value));
|
|
n--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// create name for file in given directory
|
|
static Path getTargetFile(Path dir) throws IOException {
|
|
String name = "target" + Integer.toString(rand.nextInt());
|
|
return dir.resolve(name);
|
|
}
|
|
}
|