| /* | |
| * 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); | |
| } | |
| } |