test/jdk/java/nio/file/Files/CopyAndMove.java
changeset 47216 71c04702a3d5
parent 45467 99c87a16a8e4
child 50447 3111982511ee
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/nio/file/Files/CopyAndMove.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,1215 @@
+/*
+ * Copyright (c) 2008, 2017, 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
+ * @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.RandomFactory
+ *        CopyAndMove PassThroughFileSystem
+ * @run main/othervm CopyAndMove
+ * @key randomness
+ */
+
+import java.nio.ByteBuffer;
+import java.nio.file.*;
+import static java.nio.file.Files.*;
+import static java.nio.file.StandardCopyOption.*;
+import static java.nio.file.LinkOption.*;
+import java.nio.file.attribute.*;
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import jdk.test.lib.RandomFactory;
+
+public class CopyAndMove {
+    static final Random rand = RandomFactory.getRandom();
+    static boolean heads() { return rand.nextBoolean(); }
+    private static boolean testPosixAttributes = false;
+
+    public static void main(String[] args) throws Exception {
+        Path dir1 = TestUtil.createTemporaryDirectory();
+        try {
+
+            // Same directory
+            testPosixAttributes = getFileStore(dir1).supportsFileAttributeView("posix");
+            testCopyFileToFile(dir1, dir1, TestUtil.supportsLinks(dir1));
+            testMove(dir1, dir1, TestUtil.supportsLinks(dir1));
+
+            // Different directories. Use test.dir 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);
+            try {
+                boolean testSymbolicLinks =
+                    TestUtil.supportsLinks(dir1) && TestUtil.supportsLinks(dir2);
+                testPosixAttributes = getFileStore(dir1).supportsFileAttributeView("posix") &&
+                                      getFileStore(dir2).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());
+            testPosixAttributes = getFileStore(dir1).supportsFileAttributeView("posix") &&
+                                  getFileStore(dir3).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 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);
+
+            if (time1 != time2) {
+                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()));
+        assertTrue(attrs1.owner().equals(attrs2.owner()));
+        assertTrue(attrs1.group().equals(attrs2.group()));
+    }
+
+    static void checkDosAttributes(DosFileAttributes attrs1,
+                                   DosFileAttributes attrs2)
+    {
+        assertTrue(attrs1.isReadOnly() == attrs2.isReadOnly());
+        assertTrue(attrs1.isHidden() == attrs2.isHidden());
+        assertTrue(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
+        String os = System.getProperty("os.name");
+        if (os.startsWith("Windows")) {
+            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 supportsLinks)
+        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) {
+            }
+            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 (supportsLinks) {
+            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 (supportsLinks) {
+            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 (supportsLinks) {
+            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 (supportsLinks) {
+            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 (supportsLinks) {
+            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 (supportsLinks) {
+            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 (supportsLinks) {
+            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 (supportsLinks) {
+            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));
+
+            // verify other attributes when same provider
+            if (source.getFileSystem().provider() == target.getFileSystem().provider()) {
+
+                // check POSIX attributes are copied
+                String os = System.getProperty("os.name");
+                if ((os.equals("SunOS") || os.equals("Linux")) &&
+                    testPosixAttributes)
+                {
+                    checkPosixAttributes(
+                        readAttributes(source, PosixFileAttributes.class, linkOptions),
+                        readAttributes(target, PosixFileAttributes.class, linkOptions));
+                }
+
+                // check DOS attributes are copied
+                if (os.startsWith("Windows")) {
+                    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 supportsLinks)
+        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);
+
+
+        // -- 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 (supportsLinks) {
+            source = createSourceFile(dir1);
+            link = dir1.resolve("link");
+            createSymbolicLink(link, source);
+            target = getTargetFile(dir2);
+            copyAndVerify(link, target);
+            delete(link);
+            delete(source);
+        }
+
+        /**
+         * Test: Copy link (to file)
+         */
+        if (supportsLinks) {
+            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 (supportsLinks) {
+            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 (supportsLinks) {
+            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 (supportsLinks &&
+            System.getProperty("os.name").startsWith("Windows"))
+        {
+            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.supportsLinks(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");
+    }
+
+    // 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 {
+        String os = System.getProperty("os.name");
+        boolean isWindows = os.startsWith("Windows");
+        boolean isUnix = os.equals("SunOS") || os.equals("Linux");
+        boolean isDirectory = isDirectory(file, NOFOLLOW_LINKS);
+
+        if (isUnix) {
+            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);
+        }
+
+        if (isWindows) {
+            DosFileAttributeView view =
+                getFileAttributeView(file, DosFileAttributeView.class, NOFOLLOW_LINKS);
+            // only set or unset the hidden attribute
+            view.setHidden(heads());
+        }
+
+        boolean addUserDefinedFileAttributes = heads() &&
+            getFileStore(file).supportsFileAttributeView("xattr");
+
+        // remove this when copying a direcory copies its named streams
+        if (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);
+    }
+ }