You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2015/05/25 15:06:24 UTC

[2/3] mina-sshd git commit: [SSHD-471] Auto-detect 'target' folder location and use it in the tests

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/b6166cc8/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
deleted file mode 100644
index 609d5fb..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/SftpFileSystemTest.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.channels.OverlappingFileLockException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFilePermissions;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.nio.file.attribute.UserPrincipalNotFoundException;
-import java.util.Arrays;
-import java.util.Map;
-
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.command.ScpCommandFactory;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
-import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
-import org.apache.sshd.util.Utils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class SftpFileSystemTest extends BaseTestSupport {
-
-    private SshServer sshd;
-    private int port;
-
-    @Before
-    public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
-        sshd.setCommandFactory(new ScpCommandFactory());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
-        sshd.start();
-        port = sshd.getPort();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        sshd.stop(true);
-    }
-
-    @Test
-    public void testFileSystem() throws Exception {
-        Utils.deleteRecursive(new File("target/sftp"));
-
-        String uri = "sftp://x:x@localhost:" + port + "/";
-
-        try(FileSystem fs = FileSystems.newFileSystem(URI.create(uri), null)) {
-            Iterable<Path> rootDirs = fs.getRootDirectories();
-            for (Path root : rootDirs) {
-                try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
-                    for (Path child : ds) {
-                        System.out.println(child);
-                    }
-                } catch(IOException | RuntimeException e) {
-                    // TODO on Windows one might get share problems for *.sys files
-                    // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process"
-                    // for now, Windows is less of a target so we are lenient with it
-                    if (OsUtils.isWin32()) {
-                        System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage());
-                    } else {
-                        throw e;
-                    }
-                }
-            }
-    
-            Path current = fs.getPath(".").toRealPath();
-            Path file = fs.getPath("target/sftp/client/test.txt");
-            Files.createDirectories(file.getParent());
-            Files.write(file, "Hello world\n".getBytes());
-            String buf = new String(Files.readAllBytes(file));
-            assertEquals("Hello world\n", buf);
-    
-            Path file2 = fs.getPath("target/sftp/client/test2.txt");
-            Path file3 = fs.getPath("target/sftp/client/test3.txt");
-            try {
-                Files.move(file2, file3);
-                fail("Expected an IOException");
-            } catch (NoSuchFileException e) {
-                // expected
-            }
-            Files.write(file2, "h".getBytes());
-            try {
-                Files.move(file, file2);
-                fail("Expected an IOException");
-            } catch (FileAlreadyExistsException e) {
-                // expected
-            }
-            Files.move(file, file2, StandardCopyOption.REPLACE_EXISTING);
-            Files.move(file2, file);
-    
-            Map<String, Object> attrs = Files.readAttributes(file, "*");
-            System.out.println(attrs);
-    
-            // TODO: symbolic links only work for absolute files
-    //        Path link = fs.getPath("target/sftp/client/test2.txt");
-    //        Files.createSymbolicLink(link, link.relativize(file));
-    //        assertTrue(Files.isSymbolicLink(link));
-    //        assertEquals("test.txt", Files.readSymbolicLink(link).toString());
-    
-            // TODO there are many issues with Windows and symbolic links - for now they are of a lesser interest
-            if (OsUtils.isUNIX()) {
-                Path link = fs.getPath("target/sftp/client/test2.txt");
-                Files.createSymbolicLink(link, link.getParent().relativize(file));
-                assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
-                assertEquals("test.txt", Files.readSymbolicLink(link).toString());
-                Files.delete(link);
-            }
-    
-            attrs = Files.readAttributes(file, "*", LinkOption.NOFOLLOW_LINKS);
-            System.out.println(attrs);
-    
-            buf = new String(Files.readAllBytes(file));
-            assertEquals("Hello world\n", buf);
-    
-            try (FileChannel channel = FileChannel.open(file)) {
-                try (FileLock lock = channel.lock()) {
-                    System.out.println("Locked " + lock.toString());
-    
-                    try (FileChannel channel2 = FileChannel.open(file)) {
-                        try (FileLock lock2 = channel2.lock()) {
-                            System.out.println("Locked " + lock2.toString());
-                            fail("Expected an exception");
-                        } catch (OverlappingFileLockException e) {
-                            // expected
-                        }
-                    }
-    
-                }
-            }
-    
-            Files.delete(file);
-        }
-    }
-
-    @Test
-    public void testAttributes() throws Exception {
-        Utils.deleteRecursive(new File("target/sftp"));
-
-        try (FileSystem fs = FileSystems.newFileSystem(URI.create("sftp://x:x@localhost:" + port + "/"), null)) {
-            Path file = fs.getPath("target/sftp/client/test.txt");
-            Files.createDirectories(file.getParent());
-            Files.write(file, "Hello world\n".getBytes());
-    
-            Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
-            assertNotNull("NO attributes read for " + file, attrs);
-    
-            Files.setAttribute(file, "basic:size", Long.valueOf(2l));
-            Files.setAttribute(file, "posix:permissions", PosixFilePermissions.fromString("rwxr-----"));
-            Files.setAttribute(file, "basic:lastModifiedTime", FileTime.fromMillis(100000l));
-
-            FileSystem fileSystem = file.getFileSystem();
-            try {
-                UserPrincipalLookupService userLookupService = fileSystem.getUserPrincipalLookupService();
-                GroupPrincipal group = userLookupService.lookupPrincipalByGroupName("everyone");
-                Files.setAttribute(file, "posix:group", group);
-            } catch (UserPrincipalNotFoundException e) {
-                // Also, according to the Javadoc:
-                //      "Where an implementation does not support any notion of
-                //       group then this method always throws UserPrincipalNotFoundException."
-                // Therefore we are lenient with this exception for Windows
-                if (OsUtils.isWin32()) {
-                    System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage());
-                } else {
-                    throw e;
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testRootFileSystem() throws Exception {
-        Path rootNative = Paths.get("target/root").toAbsolutePath();
-        Utils.deleteRecursive(rootNative.toFile());
-        Files.createDirectories(rootNative);
-
-        FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null);
-        Files.createDirectories(fs.getPath("test/foo"));
-    }
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/b6166cc8/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
deleted file mode 100644
index b2b0cbd..0000000
--- a/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
+++ /dev/null
@@ -1,606 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd;
-
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_FILE_ALREADY_EXISTS;
-import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_NO_SUCH_FILE;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IRUSR;
-import static org.apache.sshd.common.sftp.SftpConstants.S_IWUSR;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.util.Arrays;
-import java.util.EnumSet;
-import java.util.Vector;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.sshd.client.SftpClient;
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.command.ScpCommandFactory;
-import org.apache.sshd.server.sftp.SftpSubsystemFactory;
-import org.apache.sshd.util.BaseTestSupport;
-import org.apache.sshd.util.BogusPasswordAuthenticator;
-import org.apache.sshd.util.EchoShellFactory;
-import org.apache.sshd.util.JSchLogger;
-import org.apache.sshd.util.SimpleUserInfo;
-import org.apache.sshd.util.Utils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import com.jcraft.jsch.ChannelSftp;
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.SftpException;
-
-public class SftpTest extends BaseTestSupport {
-
-    private SshServer sshd;
-    private int port;
-    private com.jcraft.jsch.Session session;
-
-    @Before
-    public void setUp() throws Exception {
-        sshd = SshServer.setUpDefaultServer();
-        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
-        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
-        sshd.setCommandFactory(new ScpCommandFactory());
-        sshd.setShellFactory(new EchoShellFactory());
-        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
-        sshd.start();
-        port = sshd.getPort();
-
-        JSchLogger.init();
-        JSch sch = new JSch();
-        session = sch.getSession("sshd", "localhost", port);
-        session.setUserInfo(new SimpleUserInfo("sshd"));
-        session.connect();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        session.disconnect();
-        sshd.stop(true);
-    }
-
-    @Test
-    @Ignore
-    public void testExternal() throws Exception {
-        System.out.println("SFTP subsystem available on port " + port);
-        Thread.sleep(5 * 60000);
-    }
-
-    @Test
-    public void testOpen() throws Exception {
-        SshClient client = SshClient.setUpDefaultClient();
-        client.start();
-        
-        try (ClientSession session = client.connect("x", "localhost", port).await().getSession()) {
-            session.addPasswordIdentity("x");
-            session.auth().verify();
-    
-            String file = "target/sftp/client/testOpen.txt";
-            File javaFile = new File(file);
-    
-            assertHierarchyTargetFolderExists(javaFile.getParentFile());
-            javaFile.createNewFile();
-            javaFile.setWritable(false, false);
-            javaFile.setReadable(false, false);
-    
-            try (SftpClient sftp = session.createSftpClient()) {
-                SftpClient.Handle h;
-        
-                boolean	isWindows = OsUtils.isWin32();
-        
-                try {
-                    h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read));
-                    // NOTE: on Windows files are always readable
-                    // see https://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/luni/src/test/api/windows/org/apache/harmony/luni/tests/java/io/WinFileTest.java
-                    assertTrue("Empty read should have failed", isWindows);
-                    sftp.close(h);
-                } catch (IOException e) {
-                    if (isWindows) {
-                        throw e;
-                    }
-                }
-        
-                try {
-                    h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write));
-                    fail("Empty write should have failed");
-                } catch (IOException e) {
-                    // ok
-                }
-
-                try {
-                    h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate));
-                    // NOTE: on Windows files are always readable
-                    assertTrue("Empty truncate should have failed", isWindows);
-                    sftp.close(h);
-                } catch (IOException e) {
-                    // ok
-                }
-
-                // NOTE: on Windows files are always readable
-                int	perms=sftp.stat(file).perms;
-                int	permsMask=S_IWUSR | (isWindows ? 0 : S_IRUSR);
-                assertEquals("Mismatched permissions - 0x" + Integer.toHexString(perms), 0, (perms & permsMask));
-        
-                javaFile.setWritable(true, false);
-        
-                h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate, SftpClient.OpenMode.Write));
-                sftp.close(h);
-        
-                h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write));
-                byte[] d = "0123456789\n".getBytes();
-                sftp.write(h, 0, d, 0, d.length);
-                sftp.write(h, d.length, d, 0, d.length);
-                sftp.close(h);
-                h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write));
-                sftp.write(h, d.length * 2, d, 0, d.length);
-                sftp.close(h);
-                h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write));
-                sftp.write(h, 3, "-".getBytes(), 0, 1);
-                sftp.close(h);
-        
-                try {
-                    h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read));
-                    // NOTE: on Windows files are always readable
-                    assertTrue("Data read should have failed", isWindows);
-                    sftp.close(h);
-                } catch (IOException e) {
-                    if (isWindows) {
-                        throw e;
-                    }
-                }
-        
-                javaFile.setReadable(true, false);
-        
-                h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read));
-                byte[] buf = new byte[3];
-                int l = sftp.read(h, 2l, buf, 0, 3);
-                assertEquals("Mismatched read data", "2-4", new String(buf, 0, l));
-                sftp.close(h);
-            }
-        } finally {
-            client.stop();
-        }
-    }
-
-    @Test
-    public void testClient() throws Exception {
-        SshClient client = SshClient.setUpDefaultClient();
-        client.start();
-        try (ClientSession session = client.connect("x", "localhost", port).await().getSession()) {
-            session.addPasswordIdentity("x");
-            session.auth().verify(5L, TimeUnit.SECONDS);
-    
-            Utils.deleteRecursive(new File("target/sftp"));
-            new File("target/sftp").mkdirs();
-            new File("target/sftp/client/test.txt").delete();
-            new File("target/sftp/client").delete();
-    
-            try (SftpClient sftp = session.createSftpClient()) {
-                sftp.mkdir("target/sftp/client");
-        
-                SftpClient.Handle h = sftp.open("target/sftp/client/test.txt", EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create));
-                byte[] d = "0123456789\n".getBytes();
-                sftp.write(h, 0, d, 0, d.length);
-                sftp.write(h, d.length, d, 0, d.length);
-        
-                SftpClient.Attributes attrs = sftp.stat(h);
-                assertNotNull("No handle attributes", attrs);
-        
-                sftp.close(h);
-        
-                h = sftp.openDir("target/sftp/client");
-                SftpClient.DirEntry[] dir = sftp.readDir(h);
-                assertNotNull("No dir entries", dir);
-                assertEquals("Mismatced number of dir entries", 1, dir.length);
-                assertNull("Unexpected entry read", sftp.readDir(h));
-                sftp.close(h);
-        
-                sftp.remove("target/sftp/client/test.txt");
-
-                byte[] workBuf = new byte[1024 * 128];
-                try (OutputStream os = sftp.write("target/sftp/client/test.txt")) {
-                    os.write(workBuf);
-                }
-        
-                try (InputStream is = sftp.read("target/sftp/client/test.txt")) {
-                    int readLen = is.read(workBuf);
-                    assertEquals("Mismatched read data length", workBuf.length, readLen);
-    
-                    int i = is.read();
-                    assertEquals("Unexpected read past EOF", -1, i);
-                }
-    
-                SftpClient.Attributes attributes = sftp.stat("target/sftp/client/test.txt");
-                assertTrue("Test file not detected as regular", attributes.isRegularFile());
-        
-                attributes = sftp.stat("target/sftp/client");
-                assertTrue("Test directory not reported as such", attributes.isDirectory());
-        
-                int nb = 0;
-                for (SftpClient.DirEntry entry : sftp.readDir("target/sftp/client")) {
-                    assertNotNull("Unexpected null entry", entry);
-                    nb++;
-                }
-                assertEquals("Mismatched read dir entries", 1, nb);
-        
-                sftp.remove("target/sftp/client/test.txt");
-        
-                sftp.rmdir("target/sftp/client/");
-            }
-        } finally {
-            client.stop();
-        }
-    }
-
-    /**
-     * this test is meant to test out write's logic, to ensure that internal chunking (based on Buffer.MAX_LEN) is
-     * functioning properly. To do this, we write a variety of file sizes, both smaller and larger than Buffer.MAX_LEN.
-     * in addition, this test ensures that improper arguments passed in get caught with an IllegalArgumentException
-     * @throws Exception upon any uncaught exception or failure
-     */
-    @Test
-    public void testWriteChunking() throws Exception {
-        try(SshClient client = SshClient.setUpDefaultClient()) {
-            client.start();
-            
-            try(ClientSession session = client.connect("x", "localhost", port).await().getSession()) {
-                session.addPasswordIdentity("x");
-                session.auth().verify(5L, TimeUnit.SECONDS);
-        
-                Utils.deleteRecursive(new File("target/sftp"));
-                new File("target/sftp").mkdirs();
-                new File("target/sftp/client").delete();
-        
-                try(SftpClient sftp = session.createSftpClient()) {
-                    sftp.mkdir("target/sftp/client");
-            
-                    uploadAndVerifyFile(sftp, 0, "emptyFile.txt");
-                    uploadAndVerifyFile(sftp, 1000, "smallFile.txt");
-                    uploadAndVerifyFile(sftp, ByteArrayBuffer.MAX_LEN - 1, "bufferMaxLenMinusOneFile.txt");
-                    uploadAndVerifyFile(sftp, ByteArrayBuffer.MAX_LEN, "bufferMaxLenFile.txt");
-                    // were chunking not implemented, these would fail. these sizes should invoke our internal chunking mechanism
-                    uploadAndVerifyFile(sftp, ByteArrayBuffer.MAX_LEN + 1, "bufferMaxLenPlusOneFile.txt");
-                    uploadAndVerifyFile(sftp, (int)(1.5 * ByteArrayBuffer.MAX_LEN), "1point5BufferMaxLenFile.txt");
-                    uploadAndVerifyFile(sftp, (2 * ByteArrayBuffer.MAX_LEN) - 1, "2TimesBufferMaxLenMinusOneFile.txt");
-                    uploadAndVerifyFile(sftp, 2 * ByteArrayBuffer.MAX_LEN, "2TimesBufferMaxLenFile.txt");
-                    uploadAndVerifyFile(sftp, (2 * ByteArrayBuffer.MAX_LEN) + 1, "2TimesBufferMaxLenPlusOneFile.txt");
-                    uploadAndVerifyFile(sftp, 200000, "largerFile.txt");
-            
-                    // test erroneous calls that check for negative values
-                    testInvalidParams(sftp);
-            
-                    // cleanup
-                    sftp.rmdir("target/sftp/client");
-                }
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    private void testInvalidParams(SftpClient sftp) throws Exception {
-        // generate random file and upload it
-        final String filePath = "target/sftp/client/invalid";
-        SftpClient.Handle handle = sftp.open(filePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create));
-        String randomData = randomString(5);
-        try {
-            sftp.write(handle, -1, randomData.getBytes(), 0, 0);
-            fail("should not have been able to write file with invalid file offset");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-        try {
-            sftp.write(handle, 0, randomData.getBytes(), -1, 0);
-            fail("should not have been able to write file with invalid source offset");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-        try {
-            sftp.write(handle, 0, randomData.getBytes(), 0, -1);
-            fail("should not have been able to write file with invalid length");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-        try {
-            sftp.write(handle, 0, randomData.getBytes(), 0, randomData.length() + 1);
-            fail("should not have been able to write file with length bigger than array itself (no offset)");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-        try {
-            sftp.write(handle, 0, randomData.getBytes(), randomData.length(), 1);
-            fail("should not have been able to write file with length bigger than array itself (with offset)");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        // cleanup
-        sftp.close(handle);
-        sftp.remove(filePath);
-        assertFalse("file should not be there", new File(filePath).exists());
-    }
-
-    private void uploadAndVerifyFile(SftpClient sftp, int size, String filename) throws Exception {
-        // generate random file and upload it
-        final String filePath = "target/sftp/client/" + filename;
-        SftpClient.Handle handle = sftp.open(filePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create));
-        String randomData = randomString(size);
-        sftp.write(handle, 0, randomData.getBytes(), 0, randomData.length());
-        sftp.close(handle);
-
-        // verify results
-        File resultFile = new File(filePath);
-        assertTrue("file should exist on disk", resultFile.exists());
-        assertTrue("file contents should match", randomData.equals(readFile(filePath)));
-
-        // cleanup
-        sftp.remove(filePath);
-        assertFalse("file should have been removed", resultFile.exists());
-    }
-
-    @Test
-    public void testSftp() throws Exception {
-        String d = "0123456789\n";
-
-        File root = new File("target/sftp");
-        File target = new File("target/sftp/out.txt");
-        Utils.deleteRecursive(root);
-        root.mkdirs();
-        assertTrue(root.exists());
-
-        for (int j = 10; j <= 10; j++) {
-            String data = "";
-            for (int i = 0; i < j; i++) {
-                data = data + d;
-            }
-
-            target.delete();
-            assertFalse(target.exists());
-            sendFile("target/sftp/out.txt", data);
-            assertFileLength(target, data.length(), 5000);
-
-            target.delete();
-            assertFalse(target.exists());
-        }
-        root.delete();
-    }
-
-    @Test
-    public void testReadWriteWithOffset() throws Exception {
-        File root = new File("target/sftp");
-        Utils.deleteRecursive(root);
-        assertHierarchyTargetFolderExists(root);
-
-        String unixPath = "target/sftp/out.txt";
-        File target = new File(unixPath);
-
-        ChannelSftp c = (ChannelSftp) session.openChannel("sftp");
-        c.connect();
-        try {
-            c.put(new ByteArrayInputStream("0123456789".getBytes()), unixPath);
-    
-            assertTrue("Target not created after initial write: " + target.getAbsolutePath(), target.exists());
-            assertEquals("0123456789", readFile(unixPath));
-    
-            try(OutputStream os = c.put(unixPath, null, ChannelSftp.APPEND, -5)) {
-                os.write("a".getBytes());
-            }
-        } finally {
-            c.disconnect();
-        }
-
-        assertTrue("Target not created after data update: " + target.getAbsolutePath(), target.exists());
-        assertEquals("Mismatched file data", "01234a6789", readFile(unixPath));
-
-        assertTrue("Failed to delete " + target.getAbsolutePath(), target.delete());
-        assertFalse("Target not deleted: " + target.getAbsolutePath(), target.exists());
-        root.delete();
-    }
-
-    @Test
-    public void testReadDir() throws Exception {
-        ChannelSftp c = (ChannelSftp) session.openChannel("sftp");
-        c.connect();
-        try {
-            URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI();
-            URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI();
-            String path = new File(base.relativize(url).getPath()).getParent() + "/";
-            path = path.replace('\\', '/');
-            Vector<?> res = c.ls(path);
-            for (Object f : res) {
-                System.out.println(f.toString());
-            }
-        } finally {
-            c.disconnect();
-        }
-    }
-
-    @Test
-    public void testRealPath() throws Exception {
-        ChannelSftp c = (ChannelSftp) session.openChannel("sftp");
-        c.connect();
-
-        try {
-            URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI();
-            URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI();
-            String path = new File(base.relativize(url).getPath()).getParent() + "/";
-            path = path.replace('\\', '/');
-            String real = c.realpath(path);
-            System.out.println(real);
-            try {
-                real = c.realpath(path + "/foobar");
-                System.out.println(real);
-                fail("Expected SftpException");
-            } catch (SftpException e) {
-                // ok
-            }
-        } finally {
-            c.disconnect();
-        }
-    }
-
-    @Test
-    public void testRename() throws Exception {
-        try(SshClient client = SshClient.setUpDefaultClient()) {
-            client.start();
-            
-            try(ClientSession session = client.connect("x", "localhost", port).await().getSession()) {
-                session.addPasswordIdentity("x");
-                session.auth().verify(5L, TimeUnit.SECONDS);
-        
-                Utils.deleteRecursive(new File("target/sftp"));
-                new File("target/sftp").mkdirs();
-                new File("target/sftp/client").delete();
-        
-                try(SftpClient sftp = session.createSftpClient()) {
-                    try (OutputStream os = sftp.write("target/sftp/test.txt")) {
-                        os.write("Hello world!\n".getBytes());
-                    }
-            
-                    try {
-                        sftp.rename("target/sftp/test2.txt", "target/sftp/test3.txt");
-                        fail("Expected an SftpException");
-                    } catch (org.apache.sshd.client.SftpException e) {
-                        assertEquals(SSH_FX_NO_SUCH_FILE, e.getStatus());
-                    }
-            
-                    try (OutputStream os = sftp.write("target/sftp/test2.txt")) {
-                        os.write("H".getBytes());
-                    }
-            
-                    try {
-                        sftp.rename("target/sftp/test.txt", "target/sftp/test2.txt");
-                        fail("Expected an SftpException");
-                    } catch (org.apache.sshd.client.SftpException e) {
-                        assertEquals(SSH_FX_FILE_ALREADY_EXISTS, e.getStatus());
-                    }
-                    sftp.rename("target/sftp/test.txt", "target/sftp/test2.txt", SftpClient.CopyMode.Overwrite);
-                }
-            } finally {
-                client.stop();
-            }
-        }
-    }
-
-    @Test
-    public void testCreateSymbolicLink() throws Exception {
-        // Do not execute on windows as the file system does not support symlinks
-        if (System.getProperty("os.name").toLowerCase().contains("win")) {
-            return;
-        }
-
-        File root = new File("target/sftp");
-        String unixPath = "target/sftp/out.txt";
-        String linkUnixPath = "target/sftp/link.txt";
-        File target = new File(unixPath);
-        File link = new File(linkUnixPath);
-        Utils.deleteRecursive(root);
-        root.mkdirs();
-        assertTrue(root.exists());
-
-        ChannelSftp c = (ChannelSftp) session.openChannel("sftp");
-        c.connect();
-        try {
-            c.put(new ByteArrayInputStream("0123456789".getBytes()), unixPath);
-    
-            assertTrue(target.exists());
-            assertEquals("0123456789", readFile(unixPath));
-    
-            c.symlink(unixPath, linkUnixPath);
-    
-            assertTrue(link.exists());
-            assertEquals("0123456789", readFile(linkUnixPath));
-    
-            String str1 = c.readlink(linkUnixPath);
-            String str2 = c.realpath(unixPath);
-            assertEquals(str1, str2);
-        } finally {
-            c.disconnect();
-        }
-    }
-
-    protected void assertFileLength(File file, long length, long timeout) throws Exception {
-        boolean ok = false;
-        while (timeout > 0) {
-            if (file.exists() && file.length() == length) {
-                if (!ok) {
-                    ok = true;
-                } else {
-                    return;
-                }
-            } else {
-                ok = false;
-            }
-            Thread.sleep(100);
-            timeout -= 100;
-        }
-        assertTrue(file.exists());
-        assertEquals(length, file.length());
-    }
-
-    protected String readFile(String path) throws Exception {
-        ChannelSftp c = (ChannelSftp) session.openChannel("sftp");
-        c.connect();
-        
-        try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
-            InputStream is = c.get(path)) {
-
-            byte[] buffer = new byte[256];
-            int count;
-            while (-1 != (count = is.read(buffer))) {
-                bos.write(buffer, 0, count);
-            }
-
-            return bos.toString();
-        } finally {
-            c.disconnect();
-        }
-    }
-
-    protected void sendFile(String path, String data) throws Exception {
-        ChannelSftp c = (ChannelSftp) session.openChannel("sftp");
-        c.connect();
-        try {
-            c.put(new ByteArrayInputStream(data.getBytes()), path);
-        } finally {
-            c.disconnect();
-        }
-    }
-
-    private String randomString(int size) {
-        StringBuilder sb = new StringBuilder(size);
-        for (int i = 0; i < size; i++) {
-            sb.append((char) ((i % 10) + '0'));
-        }
-        return sb.toString();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/b6166cc8/sshd-core/src/test/java/org/apache/sshd/client/ScpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ScpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/ScpTest.java
new file mode 100644
index 0000000..2fb871a
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ScpTest.java
@@ -0,0 +1,935 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.ClientSession;
+import org.apache.sshd.SshClient;
+import org.apache.sshd.SshServer;
+import org.apache.sshd.common.Session;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.root.RootedFileSystemProvider;
+import org.apache.sshd.common.scp.ScpTransferEventListener;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.server.command.ScpCommandFactory;
+import org.apache.sshd.util.BaseTestSupport;
+import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShellFactory;
+import org.apache.sshd.util.JSchLogger;
+import org.apache.sshd.util.SimpleUserInfo;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.SCPClient;
+
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+
+/**
+ * Test for SCP support.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class ScpTest extends BaseTestSupport {
+
+    private SshServer sshd;
+    private int port;
+    private com.jcraft.jsch.Session session;
+    private final FileSystemFactory fileSystemFactory;
+
+    public ScpTest() throws IOException {
+        Path targetPath = detectTargetFolder().toPath();
+        Path parentPath = targetPath.getParent();
+        final FileSystem fileSystem = new RootedFileSystemProvider().newFileSystem(parentPath, Collections.<String,Object>emptyMap());
+        fileSystemFactory = new FileSystemFactory() {
+            @Override
+            public FileSystem createFileSystem(Session session) throws IOException {
+                return fileSystem;
+            }
+        };
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        sshd = SshServer.setUpDefaultServer();
+        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd.setCommandFactory(new ScpCommandFactory());
+        sshd.setShellFactory(new EchoShellFactory());
+        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
+        sshd.setFileSystemFactory(fileSystemFactory);
+        sshd.start();
+        port = sshd.getPort();
+    }
+
+    protected com.jcraft.jsch.Session getJschSession() throws JSchException {
+        JSchLogger.init();
+        JSch sch = new JSch();
+        session = sch.getSession("sshd", "localhost", port);
+        session.setUserInfo(new SimpleUserInfo("sshd"));
+        session.connect();
+        return session;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (session != null) {
+            session.disconnect();
+        }
+        
+        if (sshd != null) {
+            sshd.stop(true);
+        }
+    }
+
+    @Test
+    @Ignore
+    public void testExternal() throws Exception {
+        System.out.println("Scp available on port " + port);
+        Thread.sleep(5 * 60000);
+    }
+
+    @Test
+    public void testUploadAbsoluteDriveLetter() throws Exception {
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+
+            try {
+                try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity(getCurrentTestName());
+                    session.auth().verify(5L, TimeUnit.SECONDS);
+
+                    ScpClient scp = createScpClient(session);
+                    Path targetPath = detectTargetFolder().toPath();
+                    Path parentPath = targetPath.getParent();
+                    Path scpRoot = Utils.resolve(targetPath, "scp", getClass().getSimpleName());
+                    Utils.deleteRecursive(scpRoot);
+
+                    Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+                    Path localFile = localDir.resolve(getCurrentTestName() + "-1.txt");
+                    byte[] data = writeFile(localFile, (getCurrentTestName() + "\n"));
+
+                    Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+                    Path remoteFile = remoteDir.resolve(localFile.getFileName().toString());
+                    String localPath = localFile.toString();
+                    String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+                    scp.upload(localPath, remotePath);
+                    assertFileLength(remoteFile, data.length, 5000);
+
+                    Path secondRemote = remoteDir.resolve(getCurrentTestName() + "-2.txt");
+                    String secondPath = Utils.resolveRelativeRemotePath(parentPath, secondRemote);
+                    scp.upload(localPath, secondPath);
+                    assertFileLength(secondRemote, data.length, 5000);
+                    
+                    Path pathRemote = remoteDir.resolve(getCurrentTestName() + "-path.txt");
+                    String pathPath = Utils.resolveRelativeRemotePath(parentPath, pathRemote);
+                    scp.upload(localFile, pathPath);
+                    assertFileLength(pathRemote, data.length, 5000);
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testScpUploadOverwrite() throws Exception {
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+
+            try {
+                try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity(getCurrentTestName());
+                    session.auth().verify(5L, TimeUnit.SECONDS);
+
+                    ScpClient scp = createScpClient(session);
+                    String data = getCurrentTestName() + "\n";
+
+                    Path targetPath = detectTargetFolder().toPath();
+                    Path parentPath = targetPath.getParent();
+                    Path scpRoot = Utils.resolve(targetPath, "scp", getClass().getSimpleName());
+                    Utils.deleteRecursive(scpRoot);
+
+                    Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+                    Path localFile = localDir.resolve(getCurrentTestName() + ".txt");
+                    writeFile(localFile, data);
+
+                    Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+                    Path remoteFile = remoteDir.resolve(localFile.getFileName());
+                    writeFile(remoteFile, data + data);
+
+                    String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+                    scp.upload(localFile.toString(), remotePath);
+                    assertFileLength(remoteFile, data.length(), 5000);
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testScpUploadZeroLengthFile() throws Exception {
+        Path targetPath = detectTargetFolder().toPath();
+        Path scpRoot = Utils.resolve(targetPath, "scp", getClass().getSimpleName());
+        Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+        Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+        Path zeroLocal = localDir.resolve(getCurrentTestName());
+
+        try (FileChannel fch = FileChannel.open(zeroLocal, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
+            if (fch.size() > 0L) {
+                fch.truncate(0L);
+            }
+        }
+        assertEquals("Non-zero size for local file=" + zeroLocal, 0L, Files.size(zeroLocal));
+
+        Path zeroRemote = remoteDir.resolve(zeroLocal.getFileName());
+        if (Files.exists(zeroRemote)) {
+            Files.delete(zeroRemote);
+        }
+
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            try {
+                client.start();
+
+                try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity(getCurrentTestName());
+                    session.auth().verify(5L, TimeUnit.SECONDS);
+
+                    ScpClient scp = createScpClient(session);
+                    String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote);
+                    scp.upload(zeroLocal.toString(), remotePath);
+                    assertFileLength(zeroRemote, 0L, TimeUnit.SECONDS.toMillis(5L));
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testScpDownloadZeroLengthFile() throws Exception {
+        Path targetPath = detectTargetFolder().toPath();
+        Path scpRoot = Utils.resolve(targetPath, "scp", getClass().getSimpleName());
+        Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+        Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+        Path zeroLocal = localDir.resolve(getCurrentTestName());
+        if (Files.exists(zeroLocal)) {
+            Files.delete(zeroLocal);
+        }
+
+        Path zeroRemote = remoteDir.resolve(zeroLocal.getFileName());
+        try (FileChannel fch = FileChannel.open(zeroRemote, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
+            if (fch.size() > 0L) {
+                fch.truncate(0L);
+            }
+        }
+        assertEquals("Non-zero size for remote file=" + zeroRemote, 0L, Files.size(zeroRemote));
+
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            try {
+                client.start();
+
+                try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity(getCurrentTestName());
+                    session.auth().verify(5L, TimeUnit.SECONDS);
+
+                    ScpClient scp = createScpClient(session);
+                    String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote);
+                    scp.download(remotePath, zeroLocal.toString());
+                    assertFileLength(zeroLocal, 0L, TimeUnit.SECONDS.toMillis(5L));
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testScpNativeOnSingleFile() throws Exception {
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+
+            try {
+                try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity(getCurrentTestName());
+                    session.auth().verify(5L, TimeUnit.SECONDS);
+
+                    ScpClient scp = createScpClient(session);
+                    String data = getCurrentTestName() + "\n";
+
+                    Path targetPath = detectTargetFolder().toPath();
+                    Path parentPath = targetPath.getParent();
+                    Path scpRoot = Utils.resolve(targetPath, "scp", getClass().getSimpleName());
+                    Utils.deleteRecursive(scpRoot);
+
+                    Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+                    Path localOutFile = localDir.resolve(getCurrentTestName() + "-1.txt");
+                    writeFile(localOutFile, data);
+
+                    Path remoteDir = scpRoot.resolve("remote");
+                    Path remoteOutFile = remoteDir.resolve(localOutFile.getFileName());
+                    assertFalse("Remote folder already exists: " + remoteDir, Files.exists(remoteDir));
+                    
+                    String localOutPath = localOutFile.toString();
+                    String remoteOutPath = Utils.resolveRelativeRemotePath(parentPath, remoteOutFile);
+                    try {
+                        scp.upload(localOutPath, remoteOutPath);
+                        fail("Expected IOException for 1st time " + remoteOutPath);
+                    } catch(IOException e) {
+                        // ok
+                    }
+                    
+                    Files.createDirectories(remoteDir);
+                    scp.upload(localOutPath, remoteOutPath);
+                    assertFileLength(remoteOutFile, data.length(), 5000);
+
+                    Path secondLocal = localDir.resolve(localOutFile.getFileName());
+                    scp.download(remoteOutPath, Utils.resolveRelativeRemotePath(parentPath, secondLocal));
+                    assertFileLength(secondLocal, data.length(), 5000);
+                    
+                    Path localPath = localDir.resolve(getCurrentTestName() + "-path.txt");
+                    scp.download(remoteOutPath, Utils.resolveRelativeRemotePath(parentPath, localPath));
+                    assertFileLength(localPath, data.length(), 5000);
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testScpNativeOnMultipleFiles() throws Exception {
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+
+            try {
+                try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).await().getSession()) {
+                    session.addPasswordIdentity(getCurrentTestName());
+                    session.auth().verify(5L, TimeUnit.SECONDS);
+
+                    ScpClient scp = createScpClient(session);
+                    Path targetPath = detectTargetFolder().toPath();
+                    Path parentPath = targetPath.getParent();
+                    Path scpRoot = Utils.resolve(targetPath, "scp", getClass().getSimpleName());
+                    Utils.deleteRecursive(scpRoot);
+
+                    Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+                    Path local1 = localDir.resolve(getCurrentTestName() + "-1.txt");
+                    byte[] data = writeFile(local1, getCurrentTestName() + "\n");
+
+                    Path local2 = localDir.resolve(getCurrentTestName() + "-2.txt");
+                    Files.write(local2, data);
+
+                    Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+                    Path remote1 = remoteDir.resolve(local1.getFileName());
+                    String remote1Path = Utils.resolveRelativeRemotePath(parentPath, remote1);
+                    String[] locals = { local1.toString(), local2.toString() };
+                    try {
+                        scp.upload(locals, remote1Path);
+                        fail("Unexpected upload success to missing remote file: " + remote1Path);
+                    } catch (IOException e) {
+                        // Ok
+                    }
+                    
+                    Files.write(remote1, data);
+                    try {
+                        scp.upload(locals, remote1Path);
+                        fail("Unexpected upload success to existing remote file: " + remote1Path);
+                    } catch (IOException e) {
+                        // Ok
+                    }
+
+                    Path remoteSubDir = assertHierarchyTargetFolderExists(remoteDir.resolve("dir"));
+                    scp.upload(locals, Utils.resolveRelativeRemotePath(parentPath, remoteSubDir));
+                    
+                    Path remoteSub1 = remoteSubDir.resolve(local1.getFileName());
+                    assertFileLength(remoteSub1, data.length, 5000);
+
+                    Path remoteSub2 = remoteSubDir.resolve(local2.getFileName());
+                    assertFileLength(remoteSub2, data.length, 5000);
+
+                    String[] remotes = {
+                            Utils.resolveRelativeRemotePath(parentPath, remoteSub1),
+                            Utils.resolveRelativeRemotePath(parentPath, remoteSub2),
+                        };
+
+                    try {
+                        scp.download(remotes, Utils.resolveRelativeRemotePath(parentPath, local1));
+                        fail("Unexpected download success to existing local file: " + local1);
+                    } catch (IOException e) {
+                        // Ok
+                    }
+                    
+                    Path localSubDir = localDir.resolve("dir");
+                    try {
+                        scp.download(remotes, localSubDir);
+                        fail("Unexpected download success to non-existing folder: " + localSubDir);
+                    } catch (IOException e) {
+                        // Ok
+                    }
+
+                    Files.createDirectories(localSubDir);
+                    scp.download(remotes, localSubDir);
+
+                    assertFileLength(localSubDir.resolve(remoteSub1.getFileName()), data.length, 5000);
+                    assertFileLength(localSubDir.resolve(remoteSub2.getFileName()), data.length, 5000);
+                }
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testScpNativeOnRecursiveDirs() throws Exception {
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).await().getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+
+                ScpClient scp = createScpClient(session);
+                Path targetPath = detectTargetFolder().toPath();
+                Path parentPath = targetPath.getParent();
+                Path scpRoot = Utils.resolve(targetPath, "scp", getClass().getSimpleName());
+                Utils.deleteRecursive(scpRoot);
+
+                Path localDir = scpRoot.resolve("local");
+                Path localSubDir = assertHierarchyTargetFolderExists(localDir.resolve("dir"));
+                Path localSub1 = localSubDir.resolve(getCurrentTestName() + "-1.txt");
+                byte[] data = writeFile(localSub1, getCurrentTestName() + "\n");
+                Path localSub2 = localSubDir.resolve(getCurrentTestName() + "-2.txt");
+                Files.write(localSub2, data);
+
+                Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+                scp.upload(localSubDir, Utils.resolveRelativeRemotePath(parentPath, remoteDir), ScpClient.Option.Recursive);
+                
+                Path remoteSubDir = remoteDir.resolve(localSubDir.getFileName());
+                assertFileLength(remoteSubDir.resolve(localSub1.getFileName()), data.length, 5000);
+                assertFileLength(remoteSubDir.resolve(localSub2.getFileName()), data.length, 5000);
+
+                Utils.deleteRecursive(localSubDir);
+
+                scp.download(Utils.resolveRelativeRemotePath(parentPath, remoteSubDir), localDir, ScpClient.Option.Recursive);
+                assertFileLength(localSub1, data.length, 5000);
+                assertFileLength(localSub2, data.length, 5000);
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testScpNativeOnDirWithPattern() throws Exception {
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).await().getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+
+                ScpClient scp = createScpClient(session);
+                Path targetPath = detectTargetFolder().toPath();
+                Path parentPath = targetPath.getParent();
+                Path scpRoot = Utils.resolve(targetPath, "scp", getClass().getSimpleName());
+                Utils.deleteRecursive(scpRoot);
+
+                Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+                Path local1 = localDir.resolve(getCurrentTestName() + "-1.txt");
+                byte[] data = writeFile(local1, getCurrentTestName() + "\n");
+                Path local2 = localDir.resolve(getCurrentTestName() + "-2.txt");
+                Files.write(local2, data);
+
+                Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+                String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteDir);
+                scp.upload(localDir.toString() + File.separator + "*", remotePath);
+                assertFileLength(remoteDir.resolve(local1.getFileName()), data.length, 5000);
+                assertFileLength(remoteDir.resolve(local2.getFileName()), data.length, 5000);
+
+                Files.delete(local1);
+                Files.delete(local2);
+                scp.download(remotePath + "/*", localDir);
+                assertFileLength(local1, data.length, 5000);
+                assertFileLength(local2, data.length, 5000);
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testScpNativeOnMixedDirAndFiles() throws Exception {
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).await().getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+
+                ScpClient scp = createScpClient(session);
+                Path targetPath = detectTargetFolder().toPath();
+                Path parentPath = targetPath.getParent();
+                Path scpRoot = Utils.resolve(targetPath, "scp", getClass().getSimpleName());
+                Utils.deleteRecursive(scpRoot);
+
+                Path localDir = scpRoot.resolve("local");
+                Path localSubDir = assertHierarchyTargetFolderExists(localDir.resolve("dir"));
+                Path local1 = localDir.resolve(getCurrentTestName() + "-1.txt");
+                byte[] data = writeFile(local1, getCurrentTestName() + "\n");
+                Path localSub2 = localSubDir.resolve(getCurrentTestName() + "-2.txt");
+                Files.write(localSub2, data);
+
+                Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+                String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteDir);
+                scp.upload(localDir.toString() + File.separator + "*", remotePath, ScpClient.Option.Recursive);
+                assertFileLength(remoteDir.resolve(local1.getFileName()), data.length, 5000);
+
+                Path remoteSubDir = remoteDir.resolve(localSubDir.getFileName());
+                assertFileLength(remoteSubDir.resolve(localSub2.getFileName()), data.length, 5000);
+
+                Files.delete(local1);
+                Utils.deleteRecursive(localSubDir);
+
+                scp.download(remotePath + "/*", localDir);
+                assertFileLength(local1, data.length, 5000);
+                assertFalse("Unexpected recursive local file: " + localSub2, Files.exists(localSub2));
+
+                Files.delete(local1);
+                scp.download(remotePath + "/*", localDir, ScpClient.Option.Recursive);
+                assertFileLength(local1, data.length, 5000);
+                assertFileLength(localSub2, data.length, 5000);
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    @Test
+    public void testScpNativePreserveAttributes() throws Exception {
+        // Ignore this test if running a Windows system
+        Assume.assumeFalse("Skip test for Windows", OsUtils.isWin32());
+
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+
+
+            try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).await().getSession()) {
+                session.addPasswordIdentity(getCurrentTestName());
+                session.auth().verify(5L, TimeUnit.SECONDS);
+
+                ScpClient scp = createScpClient(session);
+                Path targetPath = detectTargetFolder().toPath();
+                Path parentPath = targetPath.getParent();
+                Path scpRoot = Utils.resolve(targetPath, "scp", getClass().getSimpleName());
+                Utils.deleteRecursive(scpRoot);
+
+                Path localDir = scpRoot.resolve("local");
+                Path localSubDir = assertHierarchyTargetFolderExists(localDir.resolve("dir"));
+                long lastMod = Files.getLastModifiedTime(localSubDir).toMillis() - TimeUnit.DAYS.toMillis(1);
+                Path local1 = localDir.resolve(getCurrentTestName() + "-1.txt");
+                byte[] data = writeFile(local1, getCurrentTestName() + "\n");
+                File lclFile1 = local1.toFile();
+                lclFile1.setLastModified(lastMod);
+                lclFile1.setExecutable(true, true);
+                lclFile1.setWritable(false, false);
+
+                Path localSub2 = localSubDir.resolve(getCurrentTestName() + "-2.txt");
+                Files.write(localSub2, data);
+                File lclSubFile2 = localSub2.toFile();
+                lclSubFile2.setLastModified(lastMod);
+
+                Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+                String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteDir);
+                scp.upload(localDir.toString() + File.separator + "*", remotePath, ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
+
+                Path remote1 = remoteDir.resolve(local1.getFileName());
+                assertFileLength(remote1, data.length, 5000);
+                
+                File remFile1 = remote1.toFile();
+                assertEquals("Mismatched uploaded last-modified time for " + remFile1, lastMod, remFile1.lastModified());
+
+                Path remoteSubDir = remoteDir.resolve(localSubDir.getFileName());
+                Path remoteSub2 = remoteSubDir.resolve(localSub2.getFileName());
+                assertFileLength(remoteSub2, data.length, 5000);
+
+                File remSubFile2 = remoteSub2.toFile();
+                assertEquals("Mismatched uploaded last-modified time for " + remSubFile2, lastMod, remSubFile2.lastModified());
+
+                Utils.deleteRecursive(localDir);
+                Files.createDirectories(localDir);
+
+                scp.download(remotePath + "/*", localDir, ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
+                assertFileLength(local1, data.length, 5000);
+                assertEquals("Mismatched downloaded last-modified time for " + lclFile1, lastMod, lclFile1.lastModified());
+                assertFileLength(localSub2, data.length, 5000);
+                assertEquals("Mismatched downloaded last-modified time for " + lclSubFile2, lastMod, lclSubFile2.lastModified());
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    private static byte[] writeFile(Path path, String data) throws IOException {
+        try(OutputStream fos = Files.newOutputStream(path)) {
+            byte[]  bytes = data.getBytes();
+            fos.write(bytes);
+            return bytes;
+        }
+    }
+
+    @Test
+    public void testJschScp() throws Exception {
+        session = getJschSession();
+        try {
+            String data = getCurrentTestName() + "\n";
+    
+            String unixDir = "target/scp";
+            String fileName = getCurrentTestName() + ".txt";
+            String unixPath = unixDir + "/" + fileName;
+            File root = new File(unixDir);
+            File target = new File(unixPath);
+            Utils.deleteRecursive(root);
+            root.mkdirs();
+            assertTrue(root.exists());
+    
+            target.delete();
+            assertFalse(target.exists());
+            sendFile(unixPath, fileName, data);
+            assertFileLength(target, data.length(), 5000);
+    
+            target.delete();
+            assertFalse(target.exists());
+            sendFile(unixDir, fileName, data);
+            assertFileLength(target, data.length(), 5000);
+    
+            sendFileError("target", "scp", data);
+    
+            readFileError(unixDir);
+    
+            assertEquals("Mismatched file data", data, readFile(unixPath, target.length()));
+            assertEquals("Mismatched dir data", data, readDir(unixDir, fileName, target.length()));
+    
+            target.delete();
+            root.delete();
+    
+            sendDir("target", "scp", fileName, data);
+            assertFileLength(target, data.length(), 5000);
+        } finally {
+            session.disconnect();
+        }
+    }
+
+    @Test
+    public void testWithGanymede() throws Exception {
+        // begin client config
+        final Connection conn = new Connection("localhost", port);
+        try {
+            conn.connect(null, 5000, 0);
+            conn.authenticateWithPassword("sshd", "sshd");
+            final SCPClient scp_client = new SCPClient(conn);
+            final Properties props = new Properties();
+            props.setProperty("test", "test-passed");
+            File f = new File("target/scp/gan");
+            Utils.deleteRecursive(f);
+            f.mkdirs();
+            assertTrue(f.exists());
+    
+            String name = "test.properties";
+            scp_client.put(toBytes(props, ""), name, "target/scp/gan");
+            assertTrue(new File(f, name).exists());
+            assertTrue(new File(f, name).delete());
+    
+            name = "test2.properties";
+            scp_client.put(toBytes(props, ""), name, "target/scp/gan");
+            assertTrue(new File(f, name).exists());
+            assertTrue(new File(f, name).delete());
+    
+            assertTrue(f.delete());
+        } finally {
+            conn.close();
+        }
+    }
+
+    private byte[] toBytes(final Properties properties, final String comments) {
+        try(ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            properties.store(baos, comments);
+            baos.close();
+            return baos.toByteArray();
+        } catch(IOException cause) {
+            throw new RuntimeException("Failed to output properties to byte[]", cause);
+        }
+    }
+
+    protected String readFile(String path, long expectedSize) throws Exception {
+        ChannelExec c = (ChannelExec) session.openChannel("exec");
+        c.setCommand("scp -f " + path);
+        c.connect();
+
+        int namePos = path.lastIndexOf('/');
+        String fileName = (namePos >= 0) ? path.substring(namePos + 1) : path;
+        try(OutputStream os = c.getOutputStream();
+            InputStream is = c.getInputStream()) {
+
+            os.write(0);
+            os.flush();
+            String header = readLine(is);
+            assertEquals("Mismatched header for " + path, "C0644 " + expectedSize + " " + fileName, header);
+
+            int length = Integer.parseInt(header.substring(6, header.indexOf(' ', 6)));
+            os.write(0);
+            os.flush();
+    
+            byte[] buffer = new byte[length];
+            length = is.read(buffer, 0, buffer.length);
+            assertEquals("Mismatched read data length for " + path, length, buffer.length);
+            assertAckReceived(is, "Read data of " + path);
+
+            os.write(0);
+            os.flush();
+
+            return new String(buffer);
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    protected String readDir(String path, String fileName, long expectedSize) throws Exception {
+        ChannelExec c = (ChannelExec) session.openChannel("exec");
+        c.setCommand("scp -r -f " + path);
+        c.connect();
+
+        try(OutputStream os = c.getOutputStream();
+            InputStream is = c.getInputStream()) {
+            os.write(0);
+            os.flush();
+
+            String header = readLine(is);
+            assertTrue("Bad header prefix for " + path + ": " + header, header.startsWith("D0755 0 "));
+            os.write(0);
+            os.flush();
+
+            header = readLine(is);
+            assertEquals("Mismatched dir header for " + path, "C0644 " + expectedSize + " " + fileName, header);
+            int length = Integer.parseInt(header.substring(6, header.indexOf(' ', 6)));
+            os.write(0);
+            os.flush();
+
+            byte[] buffer = new byte[length];
+            length = is.read(buffer, 0, buffer.length);
+            assertEquals("Mismatched read buffer size for " + path, length, buffer.length);
+            assertAckReceived(is, "Read date of " + path);
+
+            os.write(0);
+            os.flush();
+
+            header = readLine(is);
+            assertEquals("Mismatched end value for " + path, "E", header);
+            os.write(0);
+            os.flush();
+
+            return new String(buffer);
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    protected String readFileError(String path) throws Exception {
+        ChannelExec c = (ChannelExec) session.openChannel("exec");
+        String command = "scp -f " + path; 
+        c.setCommand(command);
+        c.connect();
+
+        try(OutputStream os = c.getOutputStream();
+            InputStream is = c.getInputStream()) {
+        
+            os.write(0);
+            os.flush();
+            assertEquals("Mismatched response for command: " + command, 2, is.read());
+            return null;
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    protected void sendFile(String path, String name, String data) throws Exception {
+        ChannelExec c = (ChannelExec) session.openChannel("exec");
+        String command = "scp -t " + path;
+        c.setCommand(command);
+        c.connect();
+
+        try(OutputStream os = c.getOutputStream();
+            InputStream is = c.getInputStream()) {
+
+            assertAckReceived(is, command);
+            assertAckReceived(os, is, "C7777 " + data.length() + " " + name); 
+
+            os.write(data.getBytes());
+            os.flush();
+            assertAckReceived(is, "Sent data (length=" + data.length() + ") for " + path + "[" + name + "]");
+
+            os.write(0);
+            os.flush();
+
+            Thread.sleep(100);
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    protected void assertAckReceived(OutputStream os, InputStream is, String command) throws IOException {
+        os.write((command + "\n").getBytes());
+        os.flush();
+        assertAckReceived(is, command);
+    }
+
+    protected void assertAckReceived(InputStream is, String command) throws IOException {
+        assertEquals("No ACK for command=" + command, 0, is.read());
+    }
+
+    protected void sendFileError(String path, String name, String data) throws Exception {
+        ChannelExec c = (ChannelExec) session.openChannel("exec");
+        String command = "scp -t " + path; 
+        c.setCommand(command);
+        c.connect();
+
+        try(OutputStream os = c.getOutputStream();
+            InputStream is = c.getInputStream()) {
+
+            assertAckReceived(is, command);
+
+            command = "C7777 " + data.length() + " " + name;
+            os.write((command + "\n").getBytes());
+            os.flush();
+            assertEquals("Mismatched response for command=" + command, 2, is.read());
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    protected void sendDir(String path, String dirName, String fileName, String data) throws Exception {
+        ChannelExec c = (ChannelExec) session.openChannel("exec");
+        String command = "scp -t -r " + path;
+        c.setCommand(command);
+        c.connect();
+        
+        try(OutputStream os = c.getOutputStream();
+            InputStream is = c.getInputStream()) {
+
+            assertAckReceived(is, command);
+            assertAckReceived(os, is, "D0755 0 " + dirName);
+            assertAckReceived(os, is, "C7777 " + data.length() + " " + fileName);
+
+            os.write(data.getBytes());
+            os.flush();
+            assertAckReceived(is, "Send data of " + path);
+
+            os.write(0);
+            os.flush();
+            
+            os.write("E\n".getBytes());
+            os.flush();
+            assertAckReceived(is, "Signal end of " + path);
+        } finally {
+            c.disconnect();
+        }
+    }
+
+    private static String readLine(InputStream in) throws IOException {
+        OutputStream baos = new ByteArrayOutputStream();
+        try {
+            for (; ; ) {
+                int c = in.read();
+                if (c == '\n') {
+                    return baos.toString();
+                } else if (c == -1) {
+                    throw new IOException("End of stream");
+                } else {
+                    baos.write(c);
+                }
+            }
+        } finally {
+            baos.close();
+        }
+    }
+
+    private ScpClient createScpClient(ClientSession session) {
+        final Logger logger = LoggerFactory.getLogger(getClass().getName() + "[" + getCurrentTestName() + "]");
+        return session.createScpClient(new ScpTransferEventListener() {
+            @Override
+            public void startFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms) {
+                logEvent("starFolderEvent", op, file, false, -1L, perms, null);
+            }
+
+            @Override
+            public void startFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms) {
+                logEvent("startFileEvent", op, file, true, length, perms, null);
+
+            }
+
+            @Override
+            public void endFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms, Throwable thrown) {
+                logEvent("endFolderEvent", op, file, false, -1L, perms, thrown);
+            }
+
+            @Override
+            public void endFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms, Throwable thrown) {
+                logEvent("endFileEvent", op, file, true, length, perms, thrown);
+            }
+
+            private void logEvent(String type, FileOperation op, Path path, boolean isFile, long length, Collection<PosixFilePermission> perms, Throwable t) {
+                StringBuilder sb = new StringBuilder(Byte.MAX_VALUE);
+                sb.append('\t').append(type)
+                        .append('[').append(op).append(']')
+                        .append(' ').append(isFile ? "File" : "Directory").append('=').append(path)
+                        .append(' ').append("length=").append(length)
+                        .append(' ').append("perms=").append(perms)
+                ;
+                if (t != null) {
+                    sb.append(' ').append("ERROR=").append(t.getClass().getSimpleName()).append(": ").append(t.getMessage());
+                }
+                logger.info(sb.toString());
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/b6166cc8/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java
new file mode 100644
index 0000000..3187f63
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.sftp;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.attribute.UserPrincipalNotFoundException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.sshd.SshServer;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.Session;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.root.RootedFileSystemProvider;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.command.ScpCommandFactory;
+import org.apache.sshd.server.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.BaseTestSupport;
+import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShellFactory;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SftpFileSystemTest extends BaseTestSupport {
+
+    private SshServer sshd;
+    private int port;
+    private final FileSystemFactory fileSystemFactory;
+
+    public SftpFileSystemTest() throws IOException {
+        Path targetPath = detectTargetFolder().toPath();
+        Path parentPath = targetPath.getParent();
+        final FileSystem fileSystem = new RootedFileSystemProvider().newFileSystem(parentPath, Collections.<String,Object>emptyMap());
+        fileSystemFactory = new FileSystemFactory() {
+            @Override
+            public FileSystem createFileSystem(Session session) throws IOException {
+                return fileSystem;
+            }
+        };
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        sshd = SshServer.setUpDefaultServer();
+        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
+        sshd.setCommandFactory(new ScpCommandFactory());
+        sshd.setShellFactory(new EchoShellFactory());
+        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
+        sshd.setFileSystemFactory(fileSystemFactory);
+        sshd.start();
+        port = sshd.getPort();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (sshd != null) {
+            sshd.stop(true);
+        }
+    }
+
+    @Test
+    public void testFileSystem() throws IOException {
+        Path targetPath = detectTargetFolder().toPath();
+        Path lclSftp = Utils.resolve(targetPath, "sftp", getClass().getSimpleName());
+        Utils.deleteRecursive(lclSftp);
+
+        try(FileSystem fs = FileSystems.newFileSystem(URI.create("sftp://x:x@localhost:" + port + "/"), Collections.<String,Object>emptyMap())) {
+            Iterable<Path> rootDirs = fs.getRootDirectories();
+            for (Path root : rootDirs) {
+                String  rootName = root.toString();
+                try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
+                    for (Path child : ds) {
+                        System.out.append('\t').append('[').append(rootName).append("] ").println(child);
+                    }
+                } catch(IOException | RuntimeException e) {
+                    // TODO on Windows one might get share problems for *.sys files
+                    // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process"
+                    // for now, Windows is less of a target so we are lenient with it
+                    if (OsUtils.isWin32()) {
+                        System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage());
+                    } else {
+                        throw e;
+                    }
+                }
+            }
+
+            Path current = fs.getPath(".").toRealPath().normalize();
+            System.out.append("CWD: ").println(current);
+
+            Path parentPath = targetPath.getParent();
+            Path clientFolder = lclSftp.resolve("client");
+            String remFile1Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-1.txt"));
+            Path file1 = fs.getPath(remFile1Path);
+            Files.createDirectories(file1.getParent());
+
+            String  expected="Hello world: " + getCurrentTestName();
+            {
+                Files.write(file1, expected.getBytes());
+                String buf = new String(Files.readAllBytes(file1));
+                assertEquals("Mismatched read test data", expected, buf);
+            }
+    
+            String remFile2Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-2.txt"));
+            Path file2 = fs.getPath(remFile2Path);
+            String remFile3Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-3.txt"));
+            Path file3 = fs.getPath(remFile3Path);
+            try {
+                Files.move(file2, file3);
+                fail("Unexpected success in moving " + file2 + " => " + file3);
+            } catch (NoSuchFileException e) {
+                // expected
+            }
+
+            Files.write(file2, "h".getBytes());
+            try {
+                Files.move(file1, file2);
+                fail("Unexpected success in moving " + file1 + " => " + file2);
+            } catch (FileAlreadyExistsException e) {
+                // expected
+            }
+            Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING);
+            Files.move(file2, file1);
+    
+            Map<String, Object> attrs = Files.readAttributes(file1, "*");
+            System.out.append(file1.toString()).append(" attributes: ").println(attrs);
+    
+            // TODO: symbolic links only work for absolute files
+    //        Path link = fs.getPath("target/sftp/client/test2.txt");
+    //        Files.createSymbolicLink(link, link.relativize(file));
+    //        assertTrue(Files.isSymbolicLink(link));
+    //        assertEquals("test.txt", Files.readSymbolicLink(link).toString());
+    
+            // TODO there are many issues with Windows and symbolic links - for now they are of a lesser interest
+            if (OsUtils.isUNIX()) {
+                Path link = fs.getPath(remFile2Path);
+                Path linkParent = link.getParent();
+                Path relPath = linkParent.relativize(file1);
+                Files.createSymbolicLink(link, relPath);
+                assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
+
+                Path symLink = Files.readSymbolicLink(link);
+                assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString());
+                Files.delete(link);
+            }
+    
+            attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
+            System.out.append(file1.toString()).append(" no-follow attributes: ").println(attrs);
+    
+            assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1)));
+    
+            try (FileChannel channel = FileChannel.open(file1)) {
+                try (FileLock lock = channel.lock()) {
+                    System.out.println("Locked " + lock.toString());
+    
+                    try (FileChannel channel2 = FileChannel.open(file1)) {
+                        try (FileLock lock2 = channel2.lock()) {
+                            System.out.println("Locked " + lock2.toString());
+                            fail("Unexpected success in re-locking " + file1);
+                        } catch (OverlappingFileLockException e) {
+                            // expected
+                        }
+                    }
+                }
+            }
+    
+            Files.delete(file1);
+        }
+    }
+
+    @Test
+    public void testAttributes() throws IOException {
+        Path targetPath = detectTargetFolder().toPath();
+        Path lclSftp = Utils.resolve(targetPath, "sftp", getClass().getSimpleName());
+        Utils.deleteRecursive(lclSftp);
+
+        try (FileSystem fs = FileSystems.newFileSystem(URI.create("sftp://x:x@localhost:" + port + "/"), null)) {
+            Path parentPath = targetPath.getParent();
+            Path clientFolder = lclSftp.resolve("client");
+            String remFilePath = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + ".txt"));
+            Path file = fs.getPath(remFilePath);
+            Files.createDirectories(file.getParent());
+            Files.write(file, (getCurrentTestName() + "\n").getBytes());
+    
+            Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
+            assertNotNull("NO attributes read for " + file, attrs);
+    
+            Files.setAttribute(file, "basic:size", Long.valueOf(2L));
+            Files.setAttribute(file, "posix:permissions", PosixFilePermissions.fromString("rwxr-----"));
+            Files.setAttribute(file, "basic:lastModifiedTime", FileTime.fromMillis(100000L));
+
+            FileSystem fileSystem = file.getFileSystem();
+            try {
+                UserPrincipalLookupService userLookupService = fileSystem.getUserPrincipalLookupService();
+                GroupPrincipal group = userLookupService.lookupPrincipalByGroupName("everyone");
+                Files.setAttribute(file, "posix:group", group);
+            } catch (UserPrincipalNotFoundException e) {
+                // Also, according to the Javadoc:
+                //      "Where an implementation does not support any notion of
+                //       group then this method always throws UserPrincipalNotFoundException."
+                // Therefore we are lenient with this exception for Windows
+                if (OsUtils.isWin32()) {
+                    System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage());
+                } else {
+                    throw e;
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testRootFileSystem() throws IOException {
+        Path targetPath = detectTargetFolder().toPath();
+        Path rootNative = targetPath.resolve("root").toAbsolutePath();
+        Utils.deleteRecursive(rootNative);
+        Files.createDirectories(rootNative);
+
+        try(FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) {
+            Path dir = Files.createDirectories(fs.getPath("test/foo"));
+            System.out.println("Created " + dir);
+        }
+    }
+}