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 2020/09/26 06:55:11 UTC

[mina-sshd] branch master updated (8e59075 -> 5ac112d)

This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git.


    from 8e59075  [SSHD-1080] Rework the PacketWriter to split according to the various semantics
     new eb5c510  [SSHD-1086] Added SftpPathDirectoryScanner class
     new 5ac112d  [SSHD-1086] Added SftpClientDirectoryScanner

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CHANGES.md                                         |   1 +
 docs/sftp.md                                       |  42 ++
 .../org/apache/sshd/common/util/GenericUtils.java  |  40 ++
 .../org/apache/sshd/common/util/SelectorUtils.java |  75 ++-
 .../sshd/common/util/io/DirectoryScanner.java      | 182 +------
 .../sshd/common/util/io/PathScanningMatcher.java   | 186 +++++++
 .../org/apache/sshd/common/util/io/PathUtils.java  |  76 +++
 .../sshd/common/util/io/DirectoryScannerTest.java  |   2 +-
 .../org/apache/sshd/scp/common/ScpFileOpener.java  |  23 +-
 .../sshd/scp/client/AbstractScpTestSupport.java    |  29 ++
 .../client/ScpRemote2RemoteTransferHelperTest.java |  26 +-
 .../java/org/apache/sshd/scp/client/ScpTest.java   | 105 +---
 .../org/apache/sshd/sftp/client/SftpClient.java    |  65 ++-
 .../sftp/client/fs/SftpClientDirectoryScanner.java | 226 ++++++++
 .../sftp/client/fs/SftpPathDirectoryScanner.java   |  94 ++++
 .../sftp/client/AbstractSftpClientTestSupport.java |  22 +
 .../java/org/apache/sshd/sftp/client/SftpTest.java | 567 +++++++++------------
 .../apache/sshd/sftp/client/SftpTransferTest.java  |  15 +-
 .../apache/sshd/sftp/client/SftpVersionsTest.java  | 247 ++++-----
 .../extensions/UnsupportedExtensionTest.java       |  35 +-
 .../helpers/AbstractCheckFileExtensionTest.java    |  43 +-
 .../helpers/AbstractMD5HashExtensionTest.java      |  57 +--
 .../helpers/CopyDataExtensionImplTest.java         |  17 +-
 .../helpers/CopyFileExtensionImplTest.java         |  31 +-
 .../helpers/SpaceAvailableExtensionImplTest.java   |  15 +-
 .../openssh/helpers/OpenSSHExtensionsTest.java     |  61 +--
 .../client/fs/AbstractSftpFilesSystemSupport.java  |  60 +++
 .../sftp/client/fs/SftpDirectoryScannersTest.java  | 198 +++++++
 .../sshd/sftp/client/fs/SftpFileSystemTest.java    | 164 ++----
 .../client/impl/SftpRemotePathChannelTest.java     | 106 ++--
 .../ApacheSshdSftpSessionFactoryTest.java          |  14 +-
 31 files changed, 1665 insertions(+), 1159 deletions(-)
 create mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/io/PathScanningMatcher.java
 create mode 100644 sshd-common/src/main/java/org/apache/sshd/common/util/io/PathUtils.java
 create mode 100644 sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpClientDirectoryScanner.java
 create mode 100644 sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathDirectoryScanner.java
 create mode 100644 sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/AbstractSftpFilesSystemSupport.java
 create mode 100644 sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java


[mina-sshd] 01/02: [SSHD-1086] Added SftpPathDirectoryScanner class

Posted by lg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit eb5c510505dd8a5d470252d21614f976b3572886
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Sep 25 19:11:09 2020 +0300

    [SSHD-1086] Added SftpPathDirectoryScanner class
---
 CHANGES.md                                         |   1 +
 docs/sftp.md                                       |  38 +++++
 .../org/apache/sshd/common/util/SelectorUtils.java |  75 ++++++---
 .../sshd/common/util/io/DirectoryScanner.java      |  34 ++++-
 .../sshd/common/util/io/DirectoryScannerTest.java  |   2 +-
 .../sftp/client/fs/SftpPathDirectoryScanner.java   |  94 ++++++++++++
 .../client/fs/AbstractSftpFilesSystemSupport.java  |  60 ++++++++
 .../sftp/client/fs/SftpDirectoryScannersTest.java  | 137 +++++++++++++++++
 .../sshd/sftp/client/fs/SftpFileSystemTest.java    | 167 +++++++--------------
 9 files changed, 462 insertions(+), 146 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 23f04ef..1c35b7c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -31,6 +31,7 @@ or `-key-file` command line option.
 * [SSHD-1076](https://issues.apache.org/jira/browse/SSHD-1076) Break down `ClientUserAuthService#auth` method into several to allow for flexible override
 * [SSHD-1077](https://issues.apache.org/jira/browse/SSHD-1077) Added command line option to request specific SFTP version in `SftpCommandMain`
 * [SSHD-1079](https://issues.apache.org/jira/browse/SSHD-1079) Experimental async mode on the local port forwarder
+* [SSHD-1086](https://issues.apache.org/jira/browse/SSHD-1086) Added SFTP aware directory scanning helper classes
 
 ## Behavioral changes and enhancements
 
diff --git a/docs/sftp.md b/docs/sftp.md
index 7bbfdb6..e9e6778 100644
--- a/docs/sftp.md
+++ b/docs/sftp.md
@@ -416,6 +416,44 @@ UTF-8 is used. **Note:** the value can be a charset name or a `java.nio.charset.
 
 ```
 
+### SFTP aware directory scanners
+
+The framework provides special SFTP aware directory scanners that look for files (!) matching specific patterns. The
+scanners support *recursive* scanning of the directories based on the selected patterns.
+
+E.g. - let's assume the layout present below
+
+```
+    root
+      + --- a1.txt
+      + --- a2.csv
+      + sub1
+         +--- b1.txt
+         +--- b2.csv
+      + sub2
+         + --- c1.txt
+         + --- c2.csv
+```
+
+Then scan results from `root` are expected as follows for the given patterns
+
+* "**/*" - all the files - `[a1.txt, a1.csv, b1.txt, b1.csv, c1.txt, c2.csv]`
+* "**/*.txt" - only the ".txt" files - `[a1.txt, b1.txt, c1.txt]`
+* "*" - only the files at the root - `[a1.txt, a1.csv]`
+* "*.csv" - only `a1.csv` at the root
+
+**Note:** the scanner supports various patterns - including *regex* - see `DirectoryScanner` and `SelectorUtils`
+classes for supported patterns and matching - include case sensitive vs. insensitive match.
+
+```java
+    // Using an SftpPathDirectoryScanner
+    FileSystem fs = ... obtain an SFTP file system instance ...
+    Path rootDir = fs.getPath(...remote path...);
+    DirectoryScanner ds = new SftpPathDirectoryScanner(basedir, ...pattern...);
+    Collection<Path> matches = ds.scan();
+    
+```
+
 ## Extensions
 
 Both client and server support several of the SFTP extensions specified in various drafts:
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/SelectorUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
index 9cb0a71..3bf04fd 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
@@ -93,6 +93,11 @@ public final class SelectorUtils {
      *                         &quot;**&quot;.
      */
     public static boolean matchPatternStart(String pattern, String str, boolean isCaseSensitive) {
+        return matchPath(pattern, str, File.separator, isCaseSensitive);
+    }
+
+    public static boolean matchPatternStart(
+            String pattern, String str, String separator, boolean isCaseSensitive) {
         if ((pattern.length() > (REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1))
                 && pattern.startsWith(REGEX_HANDLER_PREFIX)
                 && pattern.endsWith(PATTERN_HANDLER_SUFFIX)) {
@@ -106,14 +111,15 @@ public final class SelectorUtils {
                 pattern = pattern.substring(ANT_HANDLER_PREFIX.length(), pattern.length() - PATTERN_HANDLER_SUFFIX.length());
             }
 
-            String altStr = str.replace('\\', '/');
+            if (matchAntPathPatternStart(pattern, str, separator, isCaseSensitive)) {
+                return true;
+            }
 
-            return matchAntPathPatternStart(pattern, str, File.separator, isCaseSensitive)
-                    || matchAntPathPatternStart(pattern, altStr, "/", isCaseSensitive);
+            return matchAntPathPatternStart(pattern, str.replace('\\', '/'), "/", isCaseSensitive);
         }
     }
 
-    private static boolean matchAntPathPatternStart(
+    public static boolean matchAntPathPatternStart(
             String pattern, String str, String separator, boolean isCaseSensitive) {
         // When str starts with a File.separator, pattern has to start with a
         // File.separator.
@@ -137,8 +143,7 @@ public final class SelectorUtils {
             if (patDir.equals("**")) {
                 break;
             }
-            if (!match(patDir, strDirs.get(strIdxStart),
-                    isCaseSensitive)) {
+            if (!match(patDir, strDirs.get(strIdxStart), isCaseSensitive)) {
                 return false;
             }
             patIdxStart++;
@@ -175,7 +180,13 @@ public final class SelectorUtils {
      * @return                 <code>true</code> if the pattern matches against the string, or <code>false</code>
      *                         otherwise.
      */
-    public static boolean matchPath(String pattern, String str, boolean isCaseSensitive) {
+    public static boolean matchPath(
+            String pattern, String str, boolean isCaseSensitive) {
+        return matchPath(pattern, str, File.separator, isCaseSensitive);
+    }
+
+    public static boolean matchPath(
+            String pattern, String str, String separator, boolean isCaseSensitive) {
         if ((pattern.length() > (REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1))
                 && pattern.startsWith(REGEX_HANDLER_PREFIX)
                 && pattern.endsWith(PATTERN_HANDLER_SUFFIX)) {
@@ -188,21 +199,27 @@ public final class SelectorUtils {
                 pattern = pattern.substring(ANT_HANDLER_PREFIX.length(), pattern.length() - PATTERN_HANDLER_SUFFIX.length());
             }
 
-            return matchAntPathPattern(pattern, str, isCaseSensitive);
+            return matchAntPathPattern(pattern, str, separator, isCaseSensitive);
         }
     }
 
-    private static boolean matchAntPathPattern(String pattern, String str, boolean isCaseSensitive) {
-        // When str starts with a File.separator, pattern has to start with a
-        // File.separator.
-        // When pattern starts with a File.separator, str has to start with a
-        // File.separator.
-        if (str.startsWith(File.separator) != pattern.startsWith(File.separator)) {
+    public static boolean matchAntPathPattern(
+            String pattern, String str, boolean isCaseSensitive) {
+        return matchAntPathPattern(pattern, str, File.separator, isCaseSensitive);
+    }
+
+    public static boolean matchAntPathPattern(
+            String pattern, String str, String separator, boolean isCaseSensitive) {
+        // When str starts with a file separator, pattern has to start with a
+        // file separator.
+        // When pattern starts with a file separator, str has to start with a
+        // file separator.
+        if (str.startsWith(separator) != pattern.startsWith(separator)) {
             return false;
         }
 
-        List<String> patDirs = tokenizePath(pattern, File.separator);
-        List<String> strDirs = tokenizePath(str, File.separator);
+        List<String> patDirs = tokenizePath(pattern, separator);
+        List<String> strDirs = tokenizePath(str, separator);
 
         int patIdxStart = 0;
         int patIdxEnd = patDirs.size() - 1;
@@ -215,19 +232,23 @@ public final class SelectorUtils {
             if (patDir.equals("**")) {
                 break;
             }
-            if (!match(patDir, strDirs.get(strIdxStart),
-                    isCaseSensitive)) {
+
+            String subDir = strDirs.get(strIdxStart);
+            if (!match(patDir, subDir, isCaseSensitive)) {
                 patDirs = null;
                 strDirs = null;
                 return false;
             }
+
             patIdxStart++;
             strIdxStart++;
         }
+
         if (strIdxStart > strIdxEnd) {
             // String is exhausted
             for (int i = patIdxStart; i <= patIdxEnd; i++) {
-                if (!patDirs.get(i).equals("**")) {
+                String subPat = patDirs.get(i);
+                if (!subPat.equals("**")) {
                     patDirs = null;
                     strDirs = null;
                     return false;
@@ -249,19 +270,23 @@ public final class SelectorUtils {
             if (patDir.equals("**")) {
                 break;
             }
-            if (!match(patDir, strDirs.get(strIdxEnd),
-                    isCaseSensitive)) {
+
+            String subDir = strDirs.get(strIdxEnd);
+            if (!match(patDir, subDir, isCaseSensitive)) {
                 patDirs = null;
                 strDirs = null;
                 return false;
             }
+
             patIdxEnd--;
             strIdxEnd--;
         }
+
         if (strIdxStart > strIdxEnd) {
             // String is exhausted
             for (int i = patIdxStart; i <= patIdxEnd; i++) {
-                if (!patDirs.get(i).equals("**")) {
+                String subPat = patDirs.get(i);
+                if (!subPat.equals("**")) {
                     patDirs = null;
                     strDirs = null;
                     return false;
@@ -273,7 +298,8 @@ public final class SelectorUtils {
         while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
             int patIdxTmp = -1;
             for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
-                if (patDirs.get(i).equals("**")) {
+                String subPat = patDirs.get(i);
+                if (subPat.equals("**")) {
                     patIdxTmp = i;
                     break;
                 }
@@ -312,7 +338,8 @@ public final class SelectorUtils {
         }
 
         for (int i = patIdxStart; i <= patIdxEnd; i++) {
-            if (!patDirs.get(i).equals("**")) {
+            String subPat = patDirs.get(i);
+            if (!subPat.equals("**")) {
                 patDirs = null;
                 strDirs = null;
                 return false;
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java
index dfff44d..457dbe0 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java
@@ -35,6 +35,7 @@ import java.util.stream.Collectors;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.SelectorUtils;
+import org.apache.sshd.common.util.ValidateUtils;
 
 /**
  * <p>
@@ -104,7 +105,7 @@ import org.apache.sshd.common.util.SelectorUtils;
  * <p>
  * Example of usage:
  * </p>
- * 
+ *
  * <pre>
  * String[] includes = { "**\\*.class" };
  * String[] excludes = { "modules\\*\\**" };
@@ -134,17 +135,22 @@ public class DirectoryScanner {
     /**
      * The base directory to be scanned.
      */
-    private Path basedir;
+    protected Path basedir;
 
     /**
      * The patterns for the files to be included.
      */
-    private List<String> includePatterns;
+    protected List<String> includePatterns;
 
     /**
      * Whether or not the file system should be treated as a case sensitive one.
      */
-    private boolean caseSensitive = OsUtils.isUNIX();
+    protected boolean caseSensitive = OsUtils.isUNIX();
+
+    /**
+     * The file separator to use to parse paths - default=local O/S separator
+     */
+    protected String separator = File.separator;
 
     public DirectoryScanner() {
         super();
@@ -214,6 +220,9 @@ public class DirectoryScanner {
                                 .collect(Collectors.toCollection(() -> new ArrayList<>(includes.size()))));
     }
 
+    /**
+     * @return Whether or not the file system should be treated as a case sensitive one.
+     */
     public boolean isCaseSensitive() {
         return caseSensitive;
     }
@@ -223,6 +232,17 @@ public class DirectoryScanner {
     }
 
     /**
+     * @return The file separator to use to parse paths - default=local O/S separator
+     */
+    public String getSeparator() {
+        return separator;
+    }
+
+    public void setSeparator(String separator) {
+        this.separator = ValidateUtils.checkNotNullAndNotEmpty(separator, "No separator provided");
+    }
+
+    /**
      * Scans the base directory for files which match at least one include pattern and don't match any exclude patterns.
      * If there are selectors then the files must pass muster there, as well.
      *
@@ -303,8 +323,9 @@ public class DirectoryScanner {
         }
 
         boolean cs = isCaseSensitive();
+        String sep = getSeparator();
         for (String include : includes) {
-            if (SelectorUtils.matchPath(include, name, cs)) {
+            if (SelectorUtils.matchPath(include, name, sep, cs)) {
                 return true;
             }
         }
@@ -326,8 +347,9 @@ public class DirectoryScanner {
         }
 
         boolean cs = isCaseSensitive();
+        String sep = getSeparator();
         for (String include : includes) {
-            if (SelectorUtils.matchPatternStart(include, name, cs)) {
+            if (SelectorUtils.matchPatternStart(include, name, sep, cs)) {
                 return true;
             }
         }
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/io/DirectoryScannerTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/io/DirectoryScannerTest.java
index ded468c..1411609 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/io/DirectoryScannerTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/io/DirectoryScannerTest.java
@@ -79,7 +79,7 @@ public class DirectoryScannerTest extends JUnitTestSupport {
         Files.createDirectories(rootDir);
 
         List<Path> expected = new ArrayList<>();
-        for (int level = 1; level <= Byte.SIZE; level++) {
+        for (int level = 1; level <= 8; level++) {
             Path file = rootDir.resolve(Integer.toString(level) + (((level & 0x03) == 0) ? ".csv" : ".txt"));
             Files.write(file, Collections.singletonList(file.toString()), StandardCharsets.UTF_8);
             String name = Objects.toString(file.getFileName());
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathDirectoryScanner.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathDirectoryScanner.java
new file mode 100644
index 0000000..6bc13de
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathDirectoryScanner.java
@@ -0,0 +1,94 @@
+/*
+ * 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.sftp.client.fs;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.SelectorUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.DirectoryScanner;
+
+/**
+ * An SFTP-aware {@link DirectoryScanner} that assumes all {@link Path}-s refer to SFTP remote ones and match patterns
+ * use &quot;/&quot; as their separator with case sensitive matching by default (though the latter can be modified).
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpPathDirectoryScanner extends DirectoryScanner {
+    public SftpPathDirectoryScanner() {
+        this(true);
+    }
+
+    public SftpPathDirectoryScanner(boolean caseSensitive) {
+        setSeparator("/");
+        setCaseSensitive(caseSensitive);
+    }
+
+    public SftpPathDirectoryScanner(Path dir) {
+        this(dir, Collections.emptyList());
+    }
+
+    public SftpPathDirectoryScanner(Path dir, String... includes) {
+        this(dir, GenericUtils.isEmpty(includes) ? Collections.emptyList() : Arrays.asList(includes));
+    }
+
+    public SftpPathDirectoryScanner(Path dir, Collection<String> includes) {
+        this();
+
+        setBasedir(dir);
+        setIncludes(includes);
+    }
+
+    @Override
+    public String getSeparator() {
+        return "/";
+    }
+
+    @Override
+    public void setSeparator(String separator) {
+        ValidateUtils.checkState("/".equals(separator), "Invalid separator: '%s'", separator);
+        super.setSeparator(separator);
+    }
+
+    @Override
+    public void setIncludes(Collection<String> includes) {
+        this.includePatterns = GenericUtils.isEmpty(includes)
+                ? Collections.emptyList()
+                : Collections.unmodifiableList(
+                        includes.stream()
+                                .map(v -> adjustPattern(v))
+                                .collect(Collectors.toCollection(() -> new ArrayList<>(includes.size()))));
+    }
+
+    public static String adjustPattern(String pattern) {
+        pattern = pattern.trim();
+        if ((!pattern.startsWith(SelectorUtils.REGEX_HANDLER_PREFIX)) && pattern.endsWith("/")) {
+            return pattern + "**";
+        }
+
+        return pattern;
+    }
+}
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/AbstractSftpFilesSystemSupport.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/AbstractSftpFilesSystemSupport.java
new file mode 100644
index 0000000..7ede88f
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/AbstractSftpFilesSystemSupport.java
@@ -0,0 +1,60 @@
+/*
+ * 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.sftp.client.fs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.sftp.client.AbstractSftpClientTestSupport;
+import org.apache.sshd.sftp.client.SftpClientFactory;
+import org.apache.sshd.sftp.client.SftpVersionSelector;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractSftpFilesSystemSupport extends AbstractSftpClientTestSupport {
+    protected AbstractSftpFilesSystemSupport() throws IOException {
+        super();
+    }
+
+    protected static FileSystem createSftpFileSystem(ClientSession session, SftpVersionSelector selector) throws IOException {
+        return SftpClientFactory.instance().createSftpFileSystem(session, selector);
+    }
+
+    protected URI createDefaultFileSystemURI() {
+        return createDefaultFileSystemURI(Collections.emptyMap());
+    }
+
+    protected URI createDefaultFileSystemURI(Map<String, ?> params) {
+        return createFileSystemURI(getCurrentTestName(), params);
+    }
+
+    protected static URI createFileSystemURI(String username, Map<String, ?> params) {
+        return createFileSystemURI(username, port, params);
+    }
+
+    protected static URI createFileSystemURI(String username, int port, Map<String, ?> params) {
+        return SftpFileSystemProvider.createFileSystemURI(TEST_LOCALHOST, port, username, username, params);
+    }
+}
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java
new file mode 100644
index 0000000..612afc8
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.sftp.client.fs;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.BiPredicate;
+
+import org.apache.sshd.common.util.io.DirectoryScanner;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SftpDirectoryScannersTest extends AbstractSftpFilesSystemSupport {
+    private static final BiPredicate<Path, Path> BY_FILE_NAME = (p1, p2) -> {
+        String n1 = Objects.toString(p1.getFileName());
+        String n2 = Objects.toString(p2.getFileName());
+        return Objects.equals(n1, n2);
+    };
+
+    public SftpDirectoryScannersTest() throws IOException {
+        super();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        setupServer();
+    }
+
+    @Test
+    public void testSftpPathDirectoryScannerDeepScanning() throws IOException {
+        testSftpPathDirectoryScanner(setupDeepScanning(), "**/*");
+    }
+
+    @Test
+    public void testSftpDirectoryScannerFileSuffixMatching() throws IOException {
+        testSftpPathDirectoryScanner(setupFileSuffixMatching(), "*.txt");
+    }
+
+    private void testSftpPathDirectoryScanner(
+            Map.Entry<String, List<Path>> setup, String pattern)
+            throws IOException {
+        List<Path> expected = setup.getValue();
+        List<Path> actual;
+        try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) {
+            String remDirPath = setup.getKey();
+            Path basedir = fs.getPath(remDirPath);
+            DirectoryScanner ds = new SftpPathDirectoryScanner(basedir, pattern);
+            actual = ds.scan(() -> new ArrayList<>(expected.size()));
+        }
+        Collections.sort(actual);
+
+        assertListEquals(getCurrentTestName(), expected, actual, BY_FILE_NAME);
+    }
+
+    private Map.Entry<String, List<Path>> setupDeepScanning() throws IOException {
+        Path targetPath = detectTargetFolder();
+        Path rootDir = CommonTestSupportUtils.resolve(targetPath,
+                TEMP_SUBFOLDER_NAME, getClass().getSimpleName(), getCurrentTestName());
+        CommonTestSupportUtils.deleteRecursive(rootDir); // start fresh
+
+        List<Path> expected = new ArrayList<>();
+        Path curLevel = rootDir;
+        for (int level = 1; level <= 3; level++) {
+            Path dir = Files.createDirectories(curLevel.resolve(Integer.toString(level)));
+            expected.add(dir);
+            Path file = dir.resolve(Integer.toString(level) + ".txt");
+            Files.write(file, Collections.singletonList(file.toString()), StandardCharsets.UTF_8);
+
+            expected.add(file);
+            curLevel = dir;
+        }
+        Collections.sort(expected);
+
+        Path parentPath = targetPath.getParent();
+        String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, rootDir);
+
+        return new SimpleImmutableEntry<>(remFilePath, expected);
+    }
+
+    private Map.Entry<String, List<Path>> setupFileSuffixMatching() throws IOException {
+        Path targetPath = detectTargetFolder();
+        Path rootDir = CommonTestSupportUtils.resolve(targetPath,
+                TEMP_SUBFOLDER_NAME, getClass().getSimpleName(), getCurrentTestName());
+        CommonTestSupportUtils.deleteRecursive(rootDir); // start fresh
+        Files.createDirectories(rootDir);
+
+        List<Path> expected = new ArrayList<>();
+        for (int level = 1; level <= 8; level++) {
+            Path file = rootDir.resolve(Integer.toString(level) + (((level & 0x03) == 0) ? ".csv" : ".txt"));
+            Files.write(file, Collections.singletonList(file.toString()), StandardCharsets.UTF_8);
+            String name = Objects.toString(file.getFileName());
+            if (name.endsWith(".txt")) {
+                expected.add(file);
+            }
+        }
+        Collections.sort(expected);
+
+        Path parentPath = targetPath.getParent();
+        String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, rootDir);
+
+        return new SimpleImmutableEntry<>(remFilePath, expected);
+    }
+}
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
index ac49dd4..f41ea52 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
@@ -60,69 +60,36 @@ import java.util.Map;
 import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.file.FileSystemFactory;
-import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.MapEntryUtils.MapBuilder;
 import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.server.SshServer;
 import org.apache.sshd.sftp.SftpModuleProperties;
 import org.apache.sshd.sftp.client.SftpClient;
-import org.apache.sshd.sftp.client.SftpClientFactory;
 import org.apache.sshd.sftp.client.SftpVersionSelector;
 import org.apache.sshd.sftp.common.SftpConstants;
 import org.apache.sshd.sftp.server.SftpSubsystemEnvironment;
-import org.apache.sshd.sftp.server.SftpSubsystemFactory;
-import org.apache.sshd.util.test.BaseTestSupport;
 import org.apache.sshd.util.test.CommonTestSupportUtils;
-import org.apache.sshd.util.test.CoreTestSupportUtils;
-import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runners.MethodSorters;
 
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @SuppressWarnings("checkstyle:MethodCount")
-public class SftpFileSystemTest extends BaseTestSupport {
-    private static SshServer sshd;
-    private static int port;
-
-    private final FileSystemFactory fileSystemFactory;
-
+public class SftpFileSystemTest extends AbstractSftpFilesSystemSupport {
     public SftpFileSystemTest() throws IOException {
-        Path targetPath = detectTargetFolder();
-        Path parentPath = targetPath.getParent();
-        fileSystemFactory = new VirtualFileSystemFactory(parentPath);
-    }
-
-    @BeforeClass
-    public static void setupServerInstance() throws Exception {
-        sshd = CoreTestSupportUtils.setupTestServer(SftpFileSystemTest.class);
-        sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
-        sshd.start();
-        port = sshd.getPort();
-    }
-
-    @AfterClass
-    public static void tearDownServerInstance() throws Exception {
-        if (sshd != null) {
-            try {
-                sshd.stop(true);
-            } finally {
-                sshd = null;
-            }
-        }
+        super();
     }
 
     @Before
     public void setUp() throws Exception {
-        sshd.setFileSystemFactory(fileSystemFactory);
+        setupServer();
     }
 
     @Test
@@ -228,6 +195,8 @@ public class SftpFileSystemTest extends BaseTestSupport {
         Path targetPath = detectTargetFolder();
         Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
                 SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+        Files.createDirectories(lclSftp);
+
         Path lclFile = lclSftp.resolve(getCurrentTestName() + ".txt");
         Files.deleteIfExists(lclFile);
         byte[] expected
@@ -282,47 +251,41 @@ public class SftpFileSystemTest extends BaseTestSupport {
 
     @Test
     public void testMultipleFileStoresOnSameProvider() throws IOException {
-        try (SshClient client = setupTestClient()) {
-            client.start();
-
-            SftpFileSystemProvider provider = new SftpFileSystemProvider(client);
-            Collection<SftpFileSystem> fsList = new LinkedList<>();
-            try {
-                Collection<String> idSet = new HashSet<>();
-                Map<String, Object> empty = Collections.emptyMap();
-                for (int index = 0; index < 4; index++) {
-                    String credentials = getCurrentTestName() + "-user-" + index;
-                    SftpFileSystem expected = provider.newFileSystem(createFileSystemURI(credentials, empty), empty);
-                    fsList.add(expected);
-
-                    String id = expected.getId();
-                    assertTrue("Non unique file system id: " + id, idSet.add(id));
-
-                    SftpFileSystem actual = provider.getFileSystem(id);
-                    assertSame("Mismatched cached instances for " + id, expected, actual);
-                    outputDebugMessage("Created file system id: %s", id);
-                }
+        SftpFileSystemProvider provider = new SftpFileSystemProvider(client);
+        Collection<SftpFileSystem> fsList = new LinkedList<>();
+        try {
+            Collection<String> idSet = new HashSet<>();
+            Map<String, Object> empty = Collections.emptyMap();
+            for (int index = 0; index < 4; index++) {
+                String credentials = getCurrentTestName() + "-user-" + index;
+                SftpFileSystem expected = provider.newFileSystem(createFileSystemURI(credentials, empty), empty);
+                fsList.add(expected);
+
+                String id = expected.getId();
+                assertTrue("Non unique file system id: " + id, idSet.add(id));
+
+                SftpFileSystem actual = provider.getFileSystem(id);
+                assertSame("Mismatched cached instances for " + id, expected, actual);
+                outputDebugMessage("Created file system id: %s", id);
+            }
 
-                for (SftpFileSystem fs : fsList) {
-                    String id = fs.getId();
+            for (SftpFileSystem fs : fsList) {
+                String id = fs.getId();
+                fs.close();
+                assertNull("File system not removed from cache: " + id, provider.getFileSystem(id));
+            }
+        } finally {
+            IOException err = null;
+            for (FileSystem fs : fsList) {
+                try {
                     fs.close();
-                    assertNull("File system not removed from cache: " + id, provider.getFileSystem(id));
-                }
-            } finally {
-                IOException err = null;
-                for (FileSystem fs : fsList) {
-                    try {
-                        fs.close();
-                    } catch (IOException e) {
-                        err = GenericUtils.accumulateException(err, e);
-                    }
+                } catch (IOException e) {
+                    err = GenericUtils.accumulateException(err, e);
                 }
+            }
 
-                client.stop();
-
-                if (err != null) {
-                    throw err;
-                }
+            if (err != null) {
+                throw err;
             }
         }
     }
@@ -341,25 +304,19 @@ public class SftpFileSystemTest extends BaseTestSupport {
             return value;
         };
 
-        try (SshClient client = setupTestClient()) {
-            client.start();
-
-            try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT).getSession()) {
-                session.addPasswordIdentity(getCurrentTestName());
-                session.auth().verify(AUTH_TIMEOUT);
-
-                try (FileSystem fs = createSftpFileSystem(session, selector)) {
-                    assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
-                    Collection<String> views = fs.supportedFileAttributeViews();
-                    assertTrue("Universal views (" + SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS + ") not supported: " + views,
-                            views.containsAll(SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS));
-                    int expectedVersion = selected.get();
-                    assertEquals("Mismatched negotiated version", expectedVersion, ((SftpFileSystem) fs).getVersion());
-                    testFileSystem(fs, expectedVersion);
-                }
-            } finally {
-                client.stop();
+        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                .verify(CONNECT_TIMEOUT).getSession()) {
+            session.addPasswordIdentity(getCurrentTestName());
+            session.auth().verify(AUTH_TIMEOUT);
+
+            try (FileSystem fs = createSftpFileSystem(session, selector)) {
+                assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
+                Collection<String> views = fs.supportedFileAttributeViews();
+                assertTrue("Universal views (" + SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS + ") not supported: " + views,
+                        views.containsAll(SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS));
+                int expectedVersion = selected.get();
+                assertEquals("Mismatched negotiated version", expectedVersion, ((SftpFileSystem) fs).getVersion());
+                testFileSystem(fs, expectedVersion);
             }
         }
     }
@@ -390,10 +347,6 @@ public class SftpFileSystemTest extends BaseTestSupport {
         assertTrue("No configuration found", found);
     }
 
-    private FileSystem createSftpFileSystem(ClientSession session, SftpVersionSelector selector) throws IOException {
-        return SftpClientFactory.instance().createSftpFileSystem(session, selector);
-    }
-
     private void testFileSystem(FileSystem fs, int version) throws Exception {
         Iterable<Path> rootDirs = fs.getRootDirectories();
         for (Path root : rootDirs) {
@@ -520,20 +473,4 @@ public class SftpFileSystemTest extends BaseTestSupport {
 
         Files.delete(file1);
     }
-
-    private URI createDefaultFileSystemURI() {
-        return createDefaultFileSystemURI(Collections.emptyMap());
-    }
-
-    private URI createDefaultFileSystemURI(Map<String, ?> params) {
-        return createFileSystemURI(getCurrentTestName(), params);
-    }
-
-    private URI createFileSystemURI(String username, Map<String, ?> params) {
-        return createFileSystemURI(username, port, params);
-    }
-
-    private static URI createFileSystemURI(String username, int port, Map<String, ?> params) {
-        return SftpFileSystemProvider.createFileSystemURI(TEST_LOCALHOST, port, username, username, params);
-    }
 }


[mina-sshd] 02/02: [SSHD-1086] Added SftpClientDirectoryScanner

Posted by lg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit 5ac112d68c216d4b5551027dad703112e9be2c5c
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Sep 25 20:36:13 2020 +0300

    [SSHD-1086] Added SftpClientDirectoryScanner
---
 docs/sftp.md                                       |  10 +-
 .../org/apache/sshd/common/util/GenericUtils.java  |  40 ++
 .../sshd/common/util/io/DirectoryScanner.java      | 200 +-------
 .../sshd/common/util/io/PathScanningMatcher.java   | 186 +++++++
 .../org/apache/sshd/common/util/io/PathUtils.java  |  76 +++
 .../org/apache/sshd/scp/common/ScpFileOpener.java  |  23 +-
 .../sshd/scp/client/AbstractScpTestSupport.java    |  29 ++
 .../client/ScpRemote2RemoteTransferHelperTest.java |  26 +-
 .../java/org/apache/sshd/scp/client/ScpTest.java   | 105 +---
 .../org/apache/sshd/sftp/client/SftpClient.java    |  65 ++-
 .../sftp/client/fs/SftpClientDirectoryScanner.java | 226 ++++++++
 .../sftp/client/AbstractSftpClientTestSupport.java |  22 +
 .../java/org/apache/sshd/sftp/client/SftpTest.java | 567 +++++++++------------
 .../apache/sshd/sftp/client/SftpTransferTest.java  |  15 +-
 .../apache/sshd/sftp/client/SftpVersionsTest.java  | 247 ++++-----
 .../extensions/UnsupportedExtensionTest.java       |  35 +-
 .../helpers/AbstractCheckFileExtensionTest.java    |  43 +-
 .../helpers/AbstractMD5HashExtensionTest.java      |  57 +--
 .../helpers/CopyDataExtensionImplTest.java         |  17 +-
 .../helpers/CopyFileExtensionImplTest.java         |  31 +-
 .../helpers/SpaceAvailableExtensionImplTest.java   |  15 +-
 .../openssh/helpers/OpenSSHExtensionsTest.java     |  61 +--
 .../sftp/client/fs/SftpDirectoryScannersTest.java  | 103 +++-
 .../sshd/sftp/client/fs/SftpFileSystemTest.java    |  23 +-
 .../client/impl/SftpRemotePathChannelTest.java     | 106 ++--
 .../ApacheSshdSftpSessionFactoryTest.java          |  14 +-
 26 files changed, 1266 insertions(+), 1076 deletions(-)

diff --git a/docs/sftp.md b/docs/sftp.md
index e9e6778..7a1b15a 100644
--- a/docs/sftp.md
+++ b/docs/sftp.md
@@ -418,7 +418,7 @@ UTF-8 is used. **Note:** the value can be a charset name or a `java.nio.charset.
 
 ### SFTP aware directory scanners
 
-The framework provides special SFTP aware directory scanners that look for files (!) matching specific patterns. The
+The framework provides special SFTP aware directory scanners that look for files/folders matching specific patterns. The
 scanners support *recursive* scanning of the directories based on the selected patterns.
 
 E.g. - let's assume the layout present below
@@ -437,9 +437,9 @@ E.g. - let's assume the layout present below
 
 Then scan results from `root` are expected as follows for the given patterns
 
-* "**/*" - all the files - `[a1.txt, a1.csv, b1.txt, b1.csv, c1.txt, c2.csv]`
+* "**/*" - all the files/folders - `[a1.txt, a1.csv, sub1, sub2, b1.txt, b1.csv, c1.txt, c2.csv]`
 * "**/*.txt" - only the ".txt" files - `[a1.txt, b1.txt, c1.txt]`
-* "*" - only the files at the root - `[a1.txt, a1.csv]`
+* "*" - only the files/folders at the root - `[a1.txt, a1.csv, sub1, sub2]`
 * "*.csv" - only `a1.csv` at the root
 
 **Note:** the scanner supports various patterns - including *regex* - see `DirectoryScanner` and `SelectorUtils`
@@ -452,6 +452,10 @@ classes for supported patterns and matching - include case sensitive vs. insensi
     DirectoryScanner ds = new SftpPathDirectoryScanner(basedir, ...pattern...);
     Collection<Path> matches = ds.scan();
     
+    // Using an SftpClientDirectoryScanner
+    SftpClient client = ... obtain a client instance ...
+    SftpClientDirectoryScanner ds = new SftpClientDirectoryScanner(basedir, ...pattern...);
+    Collection<ScanDirEntry> matches = ds.scan(client);
 ```
 
 ## Extensions
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index f48a49f..9b53eb6 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -112,6 +112,46 @@ public final class GenericUtils {
     }
 
     /**
+     * <p>
+     * Replace a String with another String inside a larger String, for the first <code>max</code> values of the search
+     * String.
+     * </p>
+     *
+     * <p>
+     * A {@code null} reference passed to this method is a no-op.
+     * </p>
+     *
+     * @param  text text to search and replace in
+     * @param  repl String to search for
+     * @param  with String to replace with
+     * @param  max  maximum number of values to replace, or <code>-1</code> if no maximum
+     * @return      the text with any replacements processed
+     * @author      Arnout J. Kuiper <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
+     * @author      Magesh Umasankar
+     * @author      <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
+     * @author      <a href="mailto:levylambert@tiscali-dsl.de">Antoine Levy-Lambert</a>
+     */
+    @SuppressWarnings("PMD.AssignmentInOperand")
+    public static String replace(String text, String repl, String with, int max) {
+        if ((text == null) || (repl == null) || (with == null) || (repl.length() == 0)) {
+            return text;
+        }
+
+        int start = 0;
+        StringBuilder buf = new StringBuilder(text.length());
+        for (int end = text.indexOf(repl, start); end != -1; end = text.indexOf(repl, start)) {
+            buf.append(text.substring(start, end)).append(with);
+            start = end + repl.length();
+
+            if (--max == 0) {
+                break;
+            }
+        }
+        buf.append(text.substring(start));
+        return buf.toString();
+    }
+
+    /**
      * @param  s The {@link String} value to calculate the hash code on - may be {@code null}/empty in which case a
      *           value of zero is returned
      * @return   The calculated hash code
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java
index 457dbe0..401d494 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java
@@ -18,24 +18,19 @@
  */
 package org.apache.sshd.common.util.io;
 
-import java.io.File;
 import java.io.IOException;
 import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
-import java.util.List;
+import java.util.Objects;
 import java.util.function.Supplier;
-import java.util.stream.Collectors;
 
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.SelectorUtils;
-import org.apache.sshd.common.util.ValidateUtils;
 
 /**
  * <p>
@@ -131,27 +126,12 @@ import org.apache.sshd.common.util.ValidateUtils;
  * @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
  * @author <a href="mailto:levylambert@tiscali-dsl.de">Antoine Levy-Lambert</a>
  */
-public class DirectoryScanner {
+public class DirectoryScanner extends PathScanningMatcher {
     /**
      * The base directory to be scanned.
      */
     protected Path basedir;
 
-    /**
-     * The patterns for the files to be included.
-     */
-    protected List<String> includePatterns;
-
-    /**
-     * Whether or not the file system should be treated as a case sensitive one.
-     */
-    protected boolean caseSensitive = OsUtils.isUNIX();
-
-    /**
-     * The file separator to use to parse paths - default=local O/S separator
-     */
-    protected String separator = File.separator;
-
     public DirectoryScanner() {
         super();
     }
@@ -175,7 +155,7 @@ public class DirectoryScanner {
      * @param basedir The base directory for scanning. Should not be {@code null}.
      */
     public void setBasedir(Path basedir) {
-        this.basedir = basedir;
+        this.basedir = Objects.requireNonNull(basedir, "No base directory provided");
     }
 
     /**
@@ -188,61 +168,6 @@ public class DirectoryScanner {
     }
 
     /**
-     * <p>
-     * Sets the list of include patterns to use. All '/' and '\' characters are replaced by
-     * <code>File.separatorChar</code>, so the separator used need not match <code>File.separatorChar</code>.
-     * </p>
-     *
-     * <p>
-     * When a pattern ends with a '/' or '\', "**" is appended.
-     * </p>
-     *
-     * @param includes A list of include patterns. May be {@code null}, indicating that all files should be included. If
-     *                 a non-{@code null} list is given, all elements must be non-{@code null}.
-     */
-    public void setIncludes(String... includes) {
-        setIncludes(GenericUtils.isEmpty(includes) ? Collections.emptyList() : Arrays.asList(includes));
-    }
-
-    /**
-     * @return Un-modifiable list of the inclusion patterns
-     */
-    public List<String> getIncludes() {
-        return includePatterns;
-    }
-
-    public void setIncludes(Collection<String> includes) {
-        this.includePatterns = GenericUtils.isEmpty(includes)
-                ? Collections.emptyList()
-                : Collections.unmodifiableList(
-                        includes.stream()
-                                .map(v -> normalizePattern(v))
-                                .collect(Collectors.toCollection(() -> new ArrayList<>(includes.size()))));
-    }
-
-    /**
-     * @return Whether or not the file system should be treated as a case sensitive one.
-     */
-    public boolean isCaseSensitive() {
-        return caseSensitive;
-    }
-
-    public void setCaseSensitive(boolean caseSensitive) {
-        this.caseSensitive = caseSensitive;
-    }
-
-    /**
-     * @return The file separator to use to parse paths - default=local O/S separator
-     */
-    public String getSeparator() {
-        return separator;
-    }
-
-    public void setSeparator(String separator) {
-        this.separator = ValidateUtils.checkNotNullAndNotEmpty(separator, "No separator provided");
-    }
-
-    /**
      * Scans the base directory for files which match at least one include pattern and don't match any exclude patterns.
      * If there are selectors then the files must pass muster there, as well.
      *
@@ -270,6 +195,13 @@ public class DirectoryScanner {
             throw new IllegalStateException("No includes set for " + dir);
         }
 
+        FileSystem fs = dir.getFileSystem();
+        String fsSep = fs.getSeparator();
+        String curSep = getSeparator();
+        if (!Objects.equals(fsSep, curSep)) {
+            throw new IllegalStateException("Mismatched separator - expected=" + curSep + ", actual=" + fsSep);
+        }
+
         return scandir(dir, dir, factory.get());
     }
 
@@ -308,114 +240,4 @@ public class DirectoryScanner {
 
         return filesList;
     }
-
-    /**
-     * Tests whether or not a name matches against at least one include pattern.
-     *
-     * @param  name The name to match. Must not be {@code null}.
-     * @return      <code>true</code> when the name matches against at least one include pattern, or <code>false</code>
-     *              otherwise.
-     */
-    protected boolean isIncluded(String name) {
-        Collection<String> includes = getIncludes();
-        if (GenericUtils.isEmpty(includes)) {
-            return false;
-        }
-
-        boolean cs = isCaseSensitive();
-        String sep = getSeparator();
-        for (String include : includes) {
-            if (SelectorUtils.matchPath(include, name, sep, cs)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Tests whether or not a name matches the start of at least one include pattern.
-     *
-     * @param  name The name to match. Must not be {@code null}.
-     * @return      <code>true</code> when the name matches against the start of at least one include pattern, or
-     *              <code>false</code> otherwise.
-     */
-    protected boolean couldHoldIncluded(String name) {
-        Collection<String> includes = getIncludes();
-        if (GenericUtils.isEmpty(includes)) {
-            return false;
-        }
-
-        boolean cs = isCaseSensitive();
-        String sep = getSeparator();
-        for (String include : includes) {
-            if (SelectorUtils.matchPatternStart(include, name, sep, cs)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Normalizes the pattern, e.g. converts forward and backward slashes to the platform-specific file separator.
-     *
-     * @param  pattern The pattern to normalize, must not be {@code null}.
-     * @return         The normalized pattern, never {@code null}.
-     */
-    public static String normalizePattern(String pattern) {
-        pattern = pattern.trim();
-
-        if (pattern.startsWith(SelectorUtils.REGEX_HANDLER_PREFIX)) {
-            if (File.separatorChar == '\\') {
-                pattern = replace(pattern, "/", "\\\\", -1);
-            } else {
-                pattern = replace(pattern, "\\\\", "/", -1);
-            }
-        } else {
-            pattern = pattern.replace(File.separatorChar == '/' ? '\\' : '/', File.separatorChar);
-
-            if (pattern.endsWith(File.separator)) {
-                pattern += "**";
-            }
-        }
-
-        return pattern;
-    }
-
-    /**
-     * <p>
-     * Replace a String with another String inside a larger String, for the first <code>max</code> values of the search
-     * String.
-     * </p>
-     *
-     * <p>
-     * A {@code null} reference passed to this method is a no-op.
-     * </p>
-     *
-     * @param  text text to search and replace in
-     * @param  repl String to search for
-     * @param  with String to replace with
-     * @param  max  maximum number of values to replace, or <code>-1</code> if no maximum
-     * @return      the text with any replacements processed
-     */
-    @SuppressWarnings("PMD.AssignmentInOperand")
-    public static String replace(String text, String repl, String with, int max) {
-        if ((text == null) || (repl == null) || (with == null) || (repl.length() == 0)) {
-            return text;
-        }
-
-        int start = 0;
-        StringBuilder buf = new StringBuilder(text.length());
-        for (int end = text.indexOf(repl, start); end != -1; end = text.indexOf(repl, start)) {
-            buf.append(text.substring(start, end)).append(with);
-            start = end + repl.length();
-
-            if (--max == 0) {
-                break;
-            }
-        }
-        buf.append(text.substring(start));
-        return buf.toString();
-    }
 }
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathScanningMatcher.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathScanningMatcher.java
new file mode 100644
index 0000000..9dcaba4
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathScanningMatcher.java
@@ -0,0 +1,186 @@
+/*
+ * 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.common.util.io;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.SelectorUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class PathScanningMatcher {
+    /**
+     * Whether or not the file system should be treated as a case sensitive one.
+     */
+    protected boolean caseSensitive = OsUtils.isUNIX();
+
+    /**
+     * The file separator to use to parse paths - default=local O/S separator
+     */
+    protected String separator = File.separator;
+
+    /**
+     * The patterns for the files to be included.
+     */
+    protected List<String> includePatterns;
+
+    protected PathScanningMatcher() {
+        super();
+    }
+
+    /**
+     * <p>
+     * Sets the list of include patterns to use. All '/' and '\' characters are replaced by
+     * <code>File.separatorChar</code>, so the separator used need not match <code>File.separatorChar</code>.
+     * </p>
+     *
+     * <p>
+     * When a pattern ends with a '/' or '\', "**" is appended.
+     * </p>
+     *
+     * @param includes A list of include patterns. May be {@code null}, indicating that all files should be included. If
+     *                 a non-{@code null} list is given, all elements must be non-{@code null}.
+     */
+    public void setIncludes(String... includes) {
+        setIncludes(GenericUtils.isEmpty(includes) ? Collections.emptyList() : Arrays.asList(includes));
+    }
+
+    /**
+     * @return Un-modifiable list of the inclusion patterns
+     */
+    public List<String> getIncludes() {
+        return includePatterns;
+    }
+
+    public void setIncludes(Collection<String> includes) {
+        this.includePatterns = GenericUtils.isEmpty(includes)
+                ? Collections.emptyList()
+                : Collections.unmodifiableList(
+                        includes.stream()
+                                .map(v -> normalizePattern(v))
+                                .collect(Collectors.toCollection(() -> new ArrayList<>(includes.size()))));
+    }
+
+    /**
+     * @return Whether or not the file system should be treated as a case sensitive one.
+     */
+    public boolean isCaseSensitive() {
+        return caseSensitive;
+    }
+
+    public void setCaseSensitive(boolean caseSensitive) {
+        this.caseSensitive = caseSensitive;
+    }
+
+    /**
+     * @return The file separator to use to parse paths - default=local O/S separator
+     */
+    public String getSeparator() {
+        return separator;
+    }
+
+    public void setSeparator(String separator) {
+        this.separator = ValidateUtils.checkNotNullAndNotEmpty(separator, "No separator provided");
+    }
+
+    /**
+     * Tests whether or not a name matches against at least one include pattern.
+     *
+     * @param  name The name to match. Must not be {@code null}.
+     * @return      <code>true</code> when the name matches against at least one include pattern, or <code>false</code>
+     *              otherwise.
+     */
+    protected boolean isIncluded(String name) {
+        Collection<String> includes = getIncludes();
+        if (GenericUtils.isEmpty(includes)) {
+            return false;
+        }
+
+        boolean cs = isCaseSensitive();
+        String sep = getSeparator();
+        for (String include : includes) {
+            if (SelectorUtils.matchPath(include, name, sep, cs)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Tests whether or not a name matches the start of at least one include pattern.
+     *
+     * @param  name The name to match. Must not be {@code null}.
+     * @return      <code>true</code> when the name matches against the start of at least one include pattern, or
+     *              <code>false</code> otherwise.
+     */
+    protected boolean couldHoldIncluded(String name) {
+        Collection<String> includes = getIncludes();
+        if (GenericUtils.isEmpty(includes)) {
+            return false;
+        }
+
+        boolean cs = isCaseSensitive();
+        String sep = getSeparator();
+        for (String include : includes) {
+            if (SelectorUtils.matchPatternStart(include, name, sep, cs)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Normalizes the pattern, e.g. converts forward and backward slashes to the platform-specific file separator.
+     *
+     * @param  pattern The pattern to normalize, must not be {@code null}.
+     * @return         The normalized pattern, never {@code null}.
+     */
+    public static String normalizePattern(String pattern) {
+        pattern = pattern.trim();
+
+        if (pattern.startsWith(SelectorUtils.REGEX_HANDLER_PREFIX)) {
+            if (File.separatorChar == '\\') {
+                pattern = GenericUtils.replace(pattern, "/", "\\\\", -1);
+            } else {
+                pattern = GenericUtils.replace(pattern, "\\\\", "/", -1);
+            }
+        } else {
+            pattern = pattern.replace(File.separatorChar == '/' ? '\\' : '/', File.separatorChar);
+
+            if (pattern.endsWith(File.separator)) {
+                pattern += "**";
+            }
+        }
+
+        return pattern;
+    }
+}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathUtils.java
new file mode 100644
index 0000000..30bfd88
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/PathUtils.java
@@ -0,0 +1,76 @@
+/*
+ * 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.common.util.io;
+
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.functors.UnaryEquator;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class PathUtils {
+    /** Compares 2 {@link Path}-s by their case insensitive {@link Path#getFileName() filename} */
+    public static final Comparator<Path> BY_CASE_INSENSITIVE_FILENAME
+            = (p1, p2) -> PathUtils.safeCompareFilename(p1, p2, false);
+
+    public static final UnaryEquator<Path> EQ_CASE_INSENSITIVE_FILENAME
+            = (p1, p2) -> BY_CASE_INSENSITIVE_FILENAME.compare(p1, p2) == 0;
+
+    /** Compares 2 {@link Path}-s by their case sensitive {@link Path#getFileName() filename} */
+    public static final Comparator<Path> BY_CASE_SENSITIVE_FILENAME = (p1, p2) -> PathUtils.safeCompareFilename(p1, p2, true);
+
+    public static final UnaryEquator<Path> EQ_CASE_SENSITIVE_FILENAME
+            = (p1, p2) -> BY_CASE_SENSITIVE_FILENAME.compare(p1, p2) == 0;
+
+    /**
+     * Private Constructor
+     */
+    private PathUtils() {
+        throw new UnsupportedOperationException("No instance allowed");
+    }
+
+    /**
+     * Compares 2 {@link Path}-s by their {@link Path#getFileName() filename} while allowing for one or both to be
+     * {@code null}.
+     *
+     * @param  p1            1st {@link Path}
+     * @param  p2            2nd {@link Path}
+     * @param  caseSensitive Whether comparison is case sensitive
+     * @return               Comparison results - {@code null}-s are considered &quot;greater&quot; than
+     *                       non-{@code null}-s
+     */
+    public static int safeCompareFilename(Path p1, Path p2, boolean caseSensitive) {
+        if (GenericUtils.isSameReference(p1, p2)) {
+            return 0;
+        } else if (p1 == null) {
+            return 1;
+        } else if (p2 == null) {
+            return -1;
+        }
+
+        String n1 = Objects.toString(p1.getFileName(), null);
+        String n2 = Objects.toString(p2.getFileName(), null);
+        return GenericUtils.safeCompare(n1, n2, caseSensitive);
+    }
+}
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpFileOpener.java b/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpFileOpener.java
index fec0e66..a49890c 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpFileOpener.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/common/ScpFileOpener.java
@@ -31,15 +31,18 @@ import java.nio.file.InvalidPathException;
 import java.nio.file.LinkOption;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.nio.file.attribute.BasicFileAttributeView;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.PosixFilePermission;
+import java.util.Collections;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.SelectorUtils;
 import org.apache.sshd.common.util.io.DirectoryScanner;
 import org.apache.sshd.common.util.io.IoUtils;
@@ -119,12 +122,28 @@ public interface ScpFileOpener {
      *
      * @param  session     The client/server {@link Session} through which the transfer is being executed
      * @param  basedir     The base directory - may be {@code null}/empty to indicate CWD
-     * @param  pattern     The required pattern
+     * @param  pattern     The required pattern - ignored if {@code null}/empty - returns empty result
      * @return             The matching <U>relative paths</U> of the children to send
      * @throws IOException If failed to scan the directory
      */
     default Iterable<Path> getMatchingFilesToSend(Session session, Path basedir, String pattern) throws IOException {
-        DirectoryScanner ds = new DirectoryScanner(basedir, pattern);
+        if (GenericUtils.isEmpty(pattern)) {
+            return Collections.emptyList();
+        }
+
+        if (basedir == null) {
+            String cwdLocal = System.getProperty("user.dir");
+            Path cwdPath = Paths.get(cwdLocal);
+            basedir = cwdPath.toAbsolutePath();
+        }
+
+        // We may reach this location with a rooted path which uses '/' as the separator
+        FileSystem fs = basedir.getFileSystem();
+        String fsSep = fs.getSeparator();
+        DirectoryScanner ds = new DirectoryScanner(basedir);
+        ds.setSeparator(fsSep);
+        ds.setIncludes(Collections.singletonList(pattern));
+
         return ds.scan();
     }
 
diff --git a/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java b/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java
index eb058b2..abb0021 100644
--- a/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java
+++ b/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java
@@ -19,6 +19,7 @@
 
 package org.apache.sshd.scp.client;
 
+import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.Collection;
@@ -141,6 +142,34 @@ public abstract class AbstractScpTestSupport extends BaseTestSupport {
         }
     }
 
+    protected ClientSession createClientSession() throws IOException {
+        return client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                .verify(CONNECT_TIMEOUT)
+                .getSession();
+    }
+
+    protected ClientSession createAuthenticatedClientSession() throws IOException {
+        return createAuthenticatedClientSession(getCurrentTestName());
+    }
+
+    protected static ClientSession createAuthenticatedClientSession(String username) throws IOException {
+        ClientSession session = client.connect(username, TEST_LOCALHOST, port)
+                .verify(CONNECT_TIMEOUT)
+                .getSession();
+        try {
+            session.addPasswordIdentity(username);
+            session.auth().verify(AUTH_TIMEOUT);
+
+            ClientSession result = session;
+            session = null; // avoid auto-close at finally clause
+            return result;
+        } finally {
+            if (session != null) {
+                session.close();
+            }
+        }
+    }
+
     protected static ScpTransferEventListener getScpTransferEventListener(ClientSession session) {
         return OUTPUT_DEBUG_MESSAGES ? DEBUG_LISTENER : ScpTransferEventListener.EMPTY;
     }
diff --git a/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpRemote2RemoteTransferHelperTest.java b/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpRemote2RemoteTransferHelperTest.java
index 043a37d..2c17caf 100644
--- a/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpRemote2RemoteTransferHelperTest.java
+++ b/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpRemote2RemoteTransferHelperTest.java
@@ -142,8 +142,8 @@ public class ScpRemote2RemoteTransferHelperTest extends AbstractScpTestSupport {
         String dstPath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, dstFile);
 
         AtomicLong xferCount = new AtomicLong();
-        try (ClientSession srcSession = createClientSession(getCurrentTestName() + "-src");
-             ClientSession dstSession = createClientSession(getCurrentTestName() + "-dst")) {
+        try (ClientSession srcSession = createAuthenticatedClientSession(getCurrentTestName() + "-src");
+             ClientSession dstSession = createAuthenticatedClientSession(getCurrentTestName() + "-dst")) {
             ScpRemote2RemoteTransferHelper helper = new ScpRemote2RemoteTransferHelper(
                     srcSession, dstSession, new ScpRemote2RemoteTransferListener() {
                         @Override
@@ -222,8 +222,8 @@ public class ScpRemote2RemoteTransferHelperTest extends AbstractScpTestSupport {
 
         Path dstDir = assertHierarchyTargetFolderExists(scpRoot.resolve("dstdir"));
         String dstPath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, dstDir);
-        try (ClientSession srcSession = createClientSession(getCurrentTestName() + "-src");
-             ClientSession dstSession = createClientSession(getCurrentTestName() + "-dst")) {
+        try (ClientSession srcSession = createAuthenticatedClientSession(getCurrentTestName() + "-src");
+             ClientSession dstSession = createAuthenticatedClientSession(getCurrentTestName() + "-dst")) {
             ScpRemote2RemoteTransferHelper helper = new ScpRemote2RemoteTransferHelper(
                     srcSession, dstSession,
                     new ScpRemote2RemoteTransferListener() {
@@ -312,24 +312,6 @@ public class ScpRemote2RemoteTransferHelperTest extends AbstractScpTestSupport {
         }
     }
 
-    private ClientSession createClientSession(String username) throws IOException {
-        ClientSession session = client.connect(username, TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession();
-        try {
-            session.addPasswordIdentity(username);
-            session.auth().verify(AUTH_TIMEOUT);
-
-            ClientSession result = session;
-            session = null; // avoid auto-close at finally clause
-            return result;
-        } finally {
-            if (session != null) {
-                session.close();
-            }
-        }
-    }
-
     @Override
     public String toString() {
         return getClass().getSimpleName() + "[preserveAttributes=" + preserveAttributes + "]";
diff --git a/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpTest.java b/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpTest.java
index b7bef34..a07c097 100644
--- a/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpTest.java
+++ b/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpTest.java
@@ -120,12 +120,7 @@ public class ScpTest extends AbstractScpTestSupport {
         String[] remoteComps = GenericUtils.split(remotePath, '/');
         Factory<? extends Random> factory = client.getRandomFactory();
         Random rnd = factory.create();
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             StringBuilder sb = new StringBuilder(remotePath.length() + Long.SIZE);
             for (int i = 0; i < Math.max(Long.SIZE, remoteComps.length); i++) {
@@ -180,12 +175,7 @@ public class ScpTest extends AbstractScpTestSupport {
         Path remoteFile = remoteDir.resolve(localFile.getFileName().toString());
         String localPath = localFile.toString();
         String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, remoteFile);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             scp.upload(localPath, remotePath);
             assertFileLength(remoteFile, data.length, DEFAULT_TIMEOUT);
@@ -204,12 +194,7 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test
     public void testScpUploadOverwrite() throws Exception {
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             String data = getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL;
 
@@ -255,12 +240,7 @@ public class ScpTest extends AbstractScpTestSupport {
             Files.delete(zeroRemote);
         }
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote);
             scp.upload(zeroLocal.toString(), remotePath);
@@ -289,12 +269,7 @@ public class ScpTest extends AbstractScpTestSupport {
         }
         assertEquals("Non-zero size for remote file=" + zeroRemote, 0L, Files.size(zeroRemote));
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote);
             scp.download(remotePath, zeroLocal.toString());
@@ -320,12 +295,7 @@ public class ScpTest extends AbstractScpTestSupport {
         Path remoteDir = scpRoot.resolve("remote");
         Path remoteOutFile = remoteDir.resolve(localOutFile.getFileName());
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             CommonTestSupportUtils.writeFile(localOutFile, data);
 
@@ -365,12 +335,7 @@ public class ScpTest extends AbstractScpTestSupport {
         // see SSHD-822
         assumeNotIoServiceProvider(EnumSet.of(BuiltinIoServiceFactoryFactories.MINA, BuiltinIoServiceFactoryFactories.NETTY));
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
@@ -444,12 +409,7 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test
     public void testScpNativeOnRecursiveDirs() throws Exception {
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
@@ -484,12 +444,7 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test // see SSHD-893
     public void testScpNativeOnDirWithPattern() throws Exception {
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
@@ -526,12 +481,7 @@ public class ScpTest extends AbstractScpTestSupport {
         Files.createDirectories(remoteDir);
         sshd.setFileSystemFactory(new VirtualFileSystemFactory(remoteDir));
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             Path targetPath = detectTargetFolder();
             Path scpRoot = CommonTestSupportUtils.resolve(targetPath,
@@ -569,12 +519,7 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test
     public void testScpNativeOnMixedDirAndFiles() throws Exception {
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
@@ -614,12 +559,7 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test
     public void testScpNativePreserveAttributes() throws Exception {
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
@@ -677,12 +617,7 @@ public class ScpTest extends AbstractScpTestSupport {
 
     @Test
     public void testStreamsUploadAndDownload() throws Exception {
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClient scp = createScpClient(session);
             Path targetPath = detectTargetFolder();
             Path parentPath = targetPath.getParent();
@@ -750,12 +685,7 @@ public class ScpTest extends AbstractScpTestSupport {
         ScpFileOpener opener = factory.getScpFileOpener();
         TrackingFileOpener serverOpener = new TrackingFileOpener();
         factory.setScpFileOpener(serverOpener);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             TrackingFileOpener clientOpener = new TrackingFileOpener();
             ScpClientCreator creator = ScpClientCreator.instance();
             ScpClient scp = creator.createScpClient(session, clientOpener);
@@ -840,12 +770,7 @@ public class ScpTest extends AbstractScpTestSupport {
             }
         });
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             ScpClientCreator creator = ScpClientCreator.instance();
             ScpClient scp = creator.createScpClient(session);
             Path targetPath = detectTargetFolder();
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java
index c81ff55..d20c03f 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java
@@ -34,6 +34,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.EnumSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.NavigableMap;
@@ -461,33 +462,27 @@ public interface SftpClient extends SubsystemClient {
     }
 
     class DirEntry {
-        public static final Comparator<DirEntry> BY_CASE_SENSITIVE_FILENAME = new Comparator<DirEntry>() {
-            @Override
-            public int compare(DirEntry o1, DirEntry o2) {
-                if (o1 == o2) {
-                    return 0;
-                } else if (o1 == null) {
-                    return 1;
-                } else if (o2 == null) {
-                    return -1;
-                } else {
-                    return GenericUtils.safeCompare(o1.getFilename(), o2.getFilename(), true);
-                }
+        public static final Comparator<DirEntry> BY_CASE_SENSITIVE_FILENAME = (e1, e2) -> {
+            if (GenericUtils.isSameReference(e1, e2)) {
+                return 0;
+            } else if (e1 == null) {
+                return 1;
+            } else if (e2 == null) {
+                return -1;
+            } else {
+                return GenericUtils.safeCompare(e1.getFilename(), e2.getFilename(), true);
             }
         };
 
-        public static final Comparator<DirEntry> BY_CASE_INSENSITIVE_FILENAME = new Comparator<DirEntry>() {
-            @Override
-            public int compare(DirEntry o1, DirEntry o2) {
-                if (o1 == o2) {
-                    return 0;
-                } else if (o1 == null) {
-                    return 1;
-                } else if (o2 == null) {
-                    return -1;
-                } else {
-                    return GenericUtils.safeCompare(o1.getFilename(), o2.getFilename(), false);
-                }
+        public static final Comparator<DirEntry> BY_CASE_INSENSITIVE_FILENAME = (e1, e2) -> {
+            if (GenericUtils.isSameReference(e1, e2)) {
+                return 0;
+            } else if (e1 == null) {
+                return 1;
+            } else if (e2 == null) {
+                return -1;
+            } else {
+                return GenericUtils.safeCompare(e1.getFilename(), e2.getFilename(), false);
             }
         };
 
@@ -495,6 +490,10 @@ public interface SftpClient extends SubsystemClient {
         private final String longFilename;
         private final Attributes attributes;
 
+        public DirEntry(DirEntry other) {
+            this(other.getFilename(), other.getLongFilename(), other.getAttributes());
+        }
+
         public DirEntry(String filename, String longFilename, Attributes attributes) {
             this.filename = filename;
             this.longFilename = longFilename;
@@ -881,6 +880,24 @@ public interface SftpClient extends SubsystemClient {
      */
     Iterable<DirEntry> readDir(String path) throws IOException;
 
+    /**
+     * Reads all entries available for a directory
+     *
+     * @param  path        Remote directory path
+     * @return             A {@link Collection} of all the entries in the remote directory
+     * @throws IOException If failed to retrieve the entries
+     * @see                #readDir(String)
+     */
+    default Collection<DirEntry> readEntries(String path) throws IOException {
+        Iterable<DirEntry> iter = readDir(path);
+        Collection<DirEntry> entries = new LinkedList<>();
+        for (DirEntry d : iter) {
+            entries.add(d);
+        }
+
+        return entries;
+    }
+
     default InputStream read(String path) throws IOException {
         return read(path, 0);
     }
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpClientDirectoryScanner.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpClientDirectoryScanner.java
new file mode 100644
index 0000000..cb5287e
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpClientDirectoryScanner.java
@@ -0,0 +1,226 @@
+/*
+ * 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.sftp.client.fs;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.PathScanningMatcher;
+import org.apache.sshd.sftp.client.SftpClient;
+import org.apache.sshd.sftp.client.SftpClient.Attributes;
+import org.apache.sshd.sftp.client.SftpClient.DirEntry;
+
+/**
+ * Uses an {@link SftpClient} to scan a directory (possibly recursively) and find files that match a given set of
+ * inclusion patterns.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpClientDirectoryScanner extends PathScanningMatcher {
+    protected String basedir;
+
+    public SftpClientDirectoryScanner() {
+        this(true);
+    }
+
+    public SftpClientDirectoryScanner(boolean caseSensitive) {
+        setSeparator("/");
+        setCaseSensitive(caseSensitive);
+    }
+
+    public SftpClientDirectoryScanner(String dir) {
+        this(dir, Collections.emptyList());
+    }
+
+    public SftpClientDirectoryScanner(String dir, String... includes) {
+        this(dir, GenericUtils.isEmpty(includes) ? Collections.emptyList() : Arrays.asList(includes));
+    }
+
+    public SftpClientDirectoryScanner(String dir, Collection<String> includes) {
+        this();
+
+        setBasedir(dir);
+        setIncludes(includes);
+    }
+
+    public String getBasedir() {
+        return basedir;
+    }
+
+    /**
+     * @param basedir The base directory from which to start scanning. <B>Note:</B> it is converted to its canonical
+     *                form when scanning. May not be {@code null}/empty
+     */
+    public void setBasedir(String basedir) {
+        this.basedir = ValidateUtils.checkNotNullAndNotEmpty(basedir, "No base directory provided");
+    }
+
+    @Override
+    public String getSeparator() {
+        return "/";
+    }
+
+    @Override
+    public void setSeparator(String separator) {
+        ValidateUtils.checkState("/".equals(separator), "Invalid separator: '%s'", separator);
+        super.setSeparator(separator);
+    }
+
+    @Override
+    public void setIncludes(Collection<String> includes) {
+        this.includePatterns = GenericUtils.isEmpty(includes)
+                ? Collections.emptyList()
+                : Collections.unmodifiableList(
+                        includes.stream()
+                                .map(v -> SftpPathDirectoryScanner.adjustPattern(v))
+                                .collect(Collectors.toCollection(() -> new ArrayList<>(includes.size()))));
+    }
+
+    /**
+     * Scans the current {@link #getBasedir() basedir}
+     *
+     * @param  client                The {@link SftpClient} instance to use
+     * @return                       A {@link Collection} of {@link ScanDirEntry}-ies matching the {@link #getIncludes()
+     *                               inclusion patterns}
+     * @throws IOException           If failed to access the remote file system
+     * @throws IllegalStateException If illegal/missing base directory, or missing inclusion patterns, or specified base
+     *                               path is not a directory
+     */
+    public Collection<ScanDirEntry> scan(SftpClient client) throws IOException, IllegalStateException {
+        return scan(client, LinkedList::new);
+    }
+
+    public <C extends Collection<ScanDirEntry>> C scan(
+            SftpClient client, Supplier<? extends C> factory)
+            throws IOException, IllegalStateException {
+        String rootDir = getBasedir();
+        ValidateUtils.checkState(GenericUtils.isNotEmpty(rootDir), "No basedir set");
+        rootDir = client.canonicalPath(rootDir);
+
+        Attributes attrs = client.stat(rootDir);
+        if (attrs == null) {
+            throw new IllegalStateException("basedir " + rootDir + " does not exist");
+        }
+
+        if (!attrs.isDirectory()) {
+            throw new IllegalStateException("basedir " + rootDir + " is not a directory");
+        }
+
+        if (GenericUtils.isEmpty(getIncludes())) {
+            throw new IllegalStateException("No includes set for " + rootDir);
+        }
+
+        return scandir(client, rootDir, "", factory.get());
+    }
+
+    /**
+     * @param  <C>         Generic collection type
+     * @param  client      The {@link SftpClient} instance to use
+     * @param  rootDir     The <U>absolute</U> path of the folder to read
+     * @param  parent      The <U>relative</U> parent of the folder to read - may be empty for base directory
+     * @param  filesList   The (never {@code null}) {@link Collection} of {@link ScanDirEntry}-ies to update
+     * @return             The updated {@link Collection} of {@link ScanDirEntry}-ies
+     * @throws IOException If failed to access remote file system
+     */
+    protected <C extends Collection<ScanDirEntry>> C scandir(
+            SftpClient client, String rootDir, String parent, C filesList)
+            throws IOException {
+        Collection<DirEntry> entries = client.readEntries(rootDir);
+        if (GenericUtils.isEmpty(entries)) {
+            return filesList;
+        }
+
+        for (DirEntry de : entries) {
+            String name = de.getFilename();
+            if (".".equals(name) || "..".equals(name)) {
+                continue;
+            }
+
+            Attributes attrs = de.getAttributes();
+            if (attrs.isDirectory()) {
+                if (isIncluded(name)) {
+                    String fullPath = createRelativePath(rootDir, name);
+                    String relPath = createRelativePath(parent, name);
+                    filesList.add(new ScanDirEntry(fullPath, relPath, de));
+                    scandir(client, fullPath, relPath, filesList);
+                } else if (couldHoldIncluded(name)) {
+                    scandir(client, createRelativePath(rootDir, name), createRelativePath(parent, name), filesList);
+                }
+            } else if (attrs.isRegularFile()) {
+                if (isIncluded(name)) {
+                    filesList.add(new ScanDirEntry(createRelativePath(rootDir, name), createRelativePath(parent, name), de));
+                }
+            }
+        }
+
+        return filesList;
+    }
+
+    protected String createRelativePath(String parent, String name) {
+        if (GenericUtils.isEmpty(parent)) {
+            return name;
+        } else {
+            return parent + getSeparator() + name;
+        }
+    }
+
+    /**
+     * The result of a scan
+     *
+     * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+     */
+    public static class ScanDirEntry extends DirEntry {
+        private final String fullPath;
+        private final String relativePath;
+
+        public ScanDirEntry(String fullPath, String relativePath, DirEntry dirEntry) {
+            super(dirEntry);
+            this.fullPath = fullPath;
+            this.relativePath = relativePath;
+        }
+
+        /**
+         * @return The full path represented by this entry
+         */
+        public String getFullPath() {
+            return fullPath;
+        }
+
+        /**
+         * @return The relative path from the base directory used for scanning
+         */
+        public String getRelativePath() {
+            return relativePath;
+        }
+
+        @Override
+        public String toString() {
+            return getFullPath() + " - " + super.toString();
+        }
+    }
+}
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/AbstractSftpClientTestSupport.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/AbstractSftpClientTestSupport.java
index efaf753..9ffd69d 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/AbstractSftpClientTestSupport.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/AbstractSftpClientTestSupport.java
@@ -87,6 +87,28 @@ public abstract class AbstractSftpClientTestSupport extends BaseTestSupport {
         sshd.setFileSystemFactory(fileSystemFactory);
     }
 
+    protected ClientSession createClientSession() throws IOException {
+        return client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
+                .verify(CONNECT_TIMEOUT)
+                .getSession();
+    }
+
+    protected ClientSession createAuthenticatedClientSession() throws IOException {
+        ClientSession session = createClientSession();
+        try {
+            session.addPasswordIdentity(getCurrentTestName());
+            session.auth().verify(AUTH_TIMEOUT);
+
+            ClientSession authSession = session;
+            session = null;     // avoid auto-close at finally clause
+            return authSession;
+        } finally {
+            if (session != null) {
+                session.close();
+            }
+        }
+    }
+
     protected SftpClient createSftpClient(ClientSession session) throws IOException {
         return SftpClientFactory.instance().createSftpClient(session);
     }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java
index c7b3c36..415b3c5 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java
@@ -169,28 +169,23 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         rnd.fill(expectedRandom);
 
         byte[] expectedText = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                String file = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile);
-
-                try (CloseableHandle handle = sftp.open(
-                        file, OpenMode.Create, OpenMode.Write, OpenMode.Read, OpenMode.Append)) {
-                    sftp.write(handle, 7365L, expectedRandom);
-                    byte[] actualRandom = new byte[expectedRandom.length];
-                    int readLen = sftp.read(handle, 0L, actualRandom);
-                    assertEquals("Incomplete random data read", expectedRandom.length, readLen);
-                    assertArrayEquals("Mismatched read random data", expectedRandom, actualRandom);
-
-                    sftp.write(handle, 3777347L, expectedText);
-                    byte[] actualText = new byte[expectedText.length];
-                    readLen = sftp.read(handle, actualRandom.length, actualText);
-                    assertEquals("Incomplete text data read", actualText.length, readLen);
-                    assertArrayEquals("Mismatched read text data", expectedText, actualText);
-                }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            String file = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile);
+
+            try (CloseableHandle handle = sftp.open(
+                    file, OpenMode.Create, OpenMode.Write, OpenMode.Read, OpenMode.Append)) {
+                sftp.write(handle, 7365L, expectedRandom);
+                byte[] actualRandom = new byte[expectedRandom.length];
+                int readLen = sftp.read(handle, 0L, actualRandom);
+                assertEquals("Incomplete random data read", expectedRandom.length, readLen);
+                assertArrayEquals("Mismatched read random data", expectedRandom, actualRandom);
+
+                sftp.write(handle, 3777347L, expectedText);
+                byte[] actualText = new byte[expectedText.length];
+                readLen = sftp.read(handle, actualRandom.length, actualText);
+                assertEquals("Incomplete text data read", actualText.length, readLen);
+                assertArrayEquals("Mismatched read text data", expectedText, actualText);
             }
         }
 
@@ -219,33 +214,28 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         rnd.fill(expected);
         Files.write(testFile, expected);
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                String file = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile);
-                byte[] actual = new byte[expected.length];
-                int maxAllowed = actual.length / 4;
-                // allow less than actual
-                SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.set(sshd, maxAllowed);
-                try (CloseableHandle handle = sftp.open(file, OpenMode.Read)) {
-                    int readLen = sftp.read(handle, 0L, actual);
-                    assertEquals("Mismatched read len", maxAllowed, readLen);
-
-                    for (int index = 0; index < readLen; index++) {
-                        byte expByte = expected[index];
-                        byte actByte = actual[index];
-                        if (expByte != actByte) {
-                            fail("Mismatched values at index=" + index
-                                 + ": expected=0x" + Integer.toHexString(expByte & 0xFF)
-                                 + ", actual=0x" + Integer.toHexString(actByte & 0xFF));
-                        }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            String file = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile);
+            byte[] actual = new byte[expected.length];
+            int maxAllowed = actual.length / 4;
+            // allow less than actual
+            SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.set(sshd, maxAllowed);
+            try (CloseableHandle handle = sftp.open(file, OpenMode.Read)) {
+                int readLen = sftp.read(handle, 0L, actual);
+                assertEquals("Mismatched read len", maxAllowed, readLen);
+
+                for (int index = 0; index < readLen; index++) {
+                    byte expByte = expected[index];
+                    byte actByte = actual[index];
+                    if (expByte != actByte) {
+                        fail("Mismatched values at index=" + index
+                             + ": expected=0x" + Integer.toHexString(expByte & 0xFF)
+                             + ", actual=0x" + Integer.toHexString(actByte & 0xFF));
                     }
-                } finally {
-                    SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.remove(sshd);
                 }
+            } finally {
+                SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.remove(sshd);
             }
         }
     }
@@ -266,16 +256,11 @@ public class SftpTest extends AbstractSftpClientTestSupport {
             }
         });
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                String rootDir = sftp.canonicalPath("/");
-                String upDir = sftp.canonicalPath(rootDir + "/..");
-                assertEquals("Mismatched root dir parent", rootDir, upDir);
-            }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            String rootDir = sftp.canonicalPath("/");
+            String upDir = sftp.canonicalPath(rootDir + "/..");
+            assertEquals("Mismatched root dir parent", rootDir, upDir);
         }
     }
 
@@ -296,11 +281,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         lclSftp = assertHierarchyTargetFolderExists(lclSftp);
         sshd.setFileSystemFactory(new VirtualFileSystemFactory(lclSftp));
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             String escapePath;
             if (useAbsolutePath) {
                 escapePath = targetPath.toString();
@@ -329,27 +310,22 @@ public class SftpTest extends AbstractSftpClientTestSupport {
 
     @Test
     public void testNormalizeRemoteRootValues() throws Exception {
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                StringBuilder sb = new StringBuilder(Long.SIZE + 1);
-                String expected = sftp.canonicalPath("/");
-                for (int i = 0; i < Long.SIZE; i++) {
-                    if (sb.length() > 0) {
-                        sb.setLength(0);
-                    }
-
-                    for (int j = 1; j <= i; j++) {
-                        sb.append('/');
-                    }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            StringBuilder sb = new StringBuilder(Long.SIZE + 1);
+            String expected = sftp.canonicalPath("/");
+            for (int i = 0; i < Long.SIZE; i++) {
+                if (sb.length() > 0) {
+                    sb.setLength(0);
+                }
 
-                    String remotePath = sb.toString();
-                    String actual = sftp.canonicalPath(remotePath);
-                    assertEquals("Mismatched roots for " + remotePath.length() + " slashes", expected, actual);
+                for (int j = 1; j <= i; j++) {
+                    sb.append('/');
                 }
+
+                String remotePath = sb.toString();
+                String actual = sftp.canonicalPath(remotePath);
+                assertEquals("Mismatched roots for " + remotePath.length() + " slashes", expected, actual);
             }
         }
     }
@@ -366,35 +342,30 @@ public class SftpTest extends AbstractSftpClientTestSupport {
 
         Factory<? extends Random> factory = client.getRandomFactory();
         Random rnd = factory.create();
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                StringBuilder sb = new StringBuilder(file.length() + comps.length);
-                String expected = sftp.canonicalPath(file);
-                for (int i = 0; i < file.length(); i++) {
-                    if (sb.length() > 0) {
-                        sb.setLength(0);
-                    }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            StringBuilder sb = new StringBuilder(file.length() + comps.length);
+            String expected = sftp.canonicalPath(file);
+            for (int i = 0; i < file.length(); i++) {
+                if (sb.length() > 0) {
+                    sb.setLength(0);
+                }
 
-                    sb.append(comps[0]);
-                    for (int j = 1; j < comps.length; j++) {
-                        String name = comps[j];
-                        slashify(sb, rnd);
-                        sb.append(name);
-                    }
+                sb.append(comps[0]);
+                for (int j = 1; j < comps.length; j++) {
+                    String name = comps[j];
                     slashify(sb, rnd);
+                    sb.append(name);
+                }
+                slashify(sb, rnd);
 
-                    if (rnd.random(Byte.SIZE) < (Byte.SIZE / 2)) {
-                        sb.append('.');
-                    }
-
-                    String remotePath = sb.toString();
-                    String actual = sftp.canonicalPath(remotePath);
-                    assertEquals("Mismatched canonical value for " + remotePath, expected, actual);
+                if (rnd.random(Byte.SIZE) < (Byte.SIZE / 2)) {
+                    sb.append('.');
                 }
+
+                String remotePath = sb.toString();
+                String actual = sftp.canonicalPath(remotePath);
+                assertEquals("Mismatched canonical value for " + remotePath, expected, actual);
             }
         }
     }
@@ -421,11 +392,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         File javaFile = testFile.toFile();
         assertHierarchyTargetFolderExists(javaFile.getParentFile());
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             javaFile.createNewFile();
             javaFile.setWritable(false, false);
             javaFile.setReadable(false, false);
@@ -520,11 +487,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         String file = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile);
 
         assertHierarchyTargetFolderExists(testFile.getParent());
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             Files.deleteIfExists(testFile); // make sure starting fresh
             Files.createFile(testFile, IoUtils.EMPTY_FILE_ATTRIBUTES);
 
@@ -589,37 +552,30 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         byte[] data
                 = (getClass().getName() + "#" + getCurrentTestName() + "[" + localFile + "]").getBytes(StandardCharsets.UTF_8);
         Files.write(localFile, data, StandardOpenOption.CREATE);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session);
-                 InputStream stream = sftp.read(
-                         CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, localFile),
-                         OpenMode.Read)) {
-
-                byte[] expected = new byte[data.length / 4];
-                int readLen = expected.length;
-                System.arraycopy(data, 0, expected, 0, readLen);
-
-                byte[] actual = new byte[readLen];
-                readLen = stream.read(actual);
-                assertEquals("Failed to read fully reset data", actual.length, readLen);
-                assertArrayEquals("Mismatched re-read data contents", expected, actual);
-
-                System.arraycopy(data, 0, expected, 0, expected.length);
-                assertArrayEquals("Mismatched original data contents", expected, actual);
-
-                long skipped = stream.skip(readLen);
-                assertEquals("Mismatched skipped forward size", readLen, skipped);
-
-                readLen = stream.read(actual);
-                assertEquals("Failed to read fully skipped forward data", actual.length, readLen);
-
-                System.arraycopy(data, expected.length + readLen, expected, 0, expected.length);
-                assertArrayEquals("Mismatched skipped forward data contents", expected, actual);
-            }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session);
+             InputStream stream = sftp.read(
+                     CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, localFile), OpenMode.Read)) {
+            byte[] expected = new byte[data.length / 4];
+            int readLen = expected.length;
+            System.arraycopy(data, 0, expected, 0, readLen);
+
+            byte[] actual = new byte[readLen];
+            readLen = stream.read(actual);
+            assertEquals("Failed to read fully reset data", actual.length, readLen);
+            assertArrayEquals("Mismatched re-read data contents", expected, actual);
+
+            System.arraycopy(data, 0, expected, 0, expected.length);
+            assertArrayEquals("Mismatched original data contents", expected, actual);
+
+            long skipped = stream.skip(readLen);
+            assertEquals("Mismatched skipped forward size", readLen, skipped);
+
+            readLen = stream.read(actual);
+            assertEquals("Failed to read fully skipped forward data", actual.length, readLen);
+
+            System.arraycopy(data, expected.length + readLen, expected, 0, expected.length);
+            assertArrayEquals("Mismatched skipped forward data contents", expected, actual);
         }
     }
 
@@ -670,46 +626,41 @@ public class SftpTest extends AbstractSftpClientTestSupport {
             byte[] expected = (getClass().getName() + "#" + getCurrentTestName() + "[" + localFile + "]")
                     .getBytes(StandardCharsets.UTF_8);
             Files.write(localFile, expected, StandardOpenOption.CREATE);
-            try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT).getSession()) {
-                session.addPasswordIdentity(getCurrentTestName());
-                session.auth().verify(AUTH_TIMEOUT);
-
-                try (SftpClient sftp = createSftpClient(session)) {
-                    byte[] actual = new byte[expected.length];
-                    try (InputStream stream = sftp.read(
-                            CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, localFile), OpenMode.Read)) {
-                        IoUtils.readFully(stream, actual);
-                    }
+            try (ClientSession session = createAuthenticatedClientSession();
+                 SftpClient sftp = createSftpClient(session)) {
+                byte[] actual = new byte[expected.length];
+                try (InputStream stream = sftp.read(
+                        CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, localFile), OpenMode.Read)) {
+                    IoUtils.readFully(stream, actual);
+                }
 
-                    Path remoteFile = fileHolder.getAndSet(null);
-                    assertNotNull("No remote file holder value", remoteFile);
-                    assertEquals("Mismatched opened local files", localFile.toFile(), remoteFile.toFile());
-                    assertArrayEquals("Mismatched retrieved file contents", expected, actual);
-
-                    Path localParent = localFile.getParent();
-                    String localName = Objects.toString(localFile.getFileName(), null);
-                    try (CloseableHandle handle = sftp.openDir(
-                            CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, localParent))) {
-                        List<DirEntry> entries = sftp.readDir(handle);
-                        Path remoteParent = dirHolder.getAndSet(null);
-                        assertNotNull("No remote folder holder value", remoteParent);
-                        assertEquals("Mismatched opened folder", localParent.toFile(), remoteParent.toFile());
-                        assertFalse("No dir entries", GenericUtils.isEmpty(entries));
-
-                        for (DirEntry de : entries) {
-                            Attributes attrs = de.getAttributes();
-                            if (!attrs.isRegularFile()) {
-                                continue;
-                            }
-
-                            if (localName.equals(de.getFilename())) {
-                                return;
-                            }
+                Path remoteFile = fileHolder.getAndSet(null);
+                assertNotNull("No remote file holder value", remoteFile);
+                assertEquals("Mismatched opened local files", localFile.toFile(), remoteFile.toFile());
+                assertArrayEquals("Mismatched retrieved file contents", expected, actual);
+
+                Path localParent = localFile.getParent();
+                String localName = Objects.toString(localFile.getFileName(), null);
+                try (CloseableHandle handle = sftp.openDir(
+                        CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, localParent))) {
+                    List<DirEntry> entries = sftp.readDir(handle);
+                    Path remoteParent = dirHolder.getAndSet(null);
+                    assertNotNull("No remote folder holder value", remoteParent);
+                    assertEquals("Mismatched opened folder", localParent.toFile(), remoteParent.toFile());
+                    assertFalse("No dir entries", GenericUtils.isEmpty(entries));
+
+                    for (DirEntry de : entries) {
+                        Attributes attrs = de.getAttributes();
+                        if (!attrs.isRegularFile()) {
+                            continue;
                         }
 
-                        fail("Cannot find listing of " + localName);
+                        if (localName.equals(de.getFilename())) {
+                            return;
+                        }
                     }
+
+                    fail("Cannot find listing of " + localName);
                 }
             }
         } finally {
@@ -914,15 +865,10 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         };
         factory.addSftpEventListener(listener);
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                assertEquals("Mismatched negotiated version", sftp.getVersion(), versionHolder.get());
-                testClient(client, sftp);
-            }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            assertEquals("Mismatched negotiated version", sftp.getVersion(), versionHolder.get());
+            testClient(client, sftp);
 
             assertEquals("Mismatched open/close count", openCount.get(), closeCount.get());
             assertTrue("No entries read", entriesCount.get() > 0);
@@ -949,11 +895,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
      */
     @Test
     public void testWriteChunking() throws Exception {
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             Path targetPath = detectTargetFolder();
             Path lclSftp = CommonTestSupportUtils.resolve(
                     targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
@@ -1183,114 +1125,104 @@ public class SftpTest extends AbstractSftpClientTestSupport {
 
         Path parentPath = targetPath.getParent();
         Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client"));
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                Path file1 = clientFolder.resolve("file-1.txt");
-                String file1Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, file1);
-                try (OutputStream os = sftp.write(file1Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
-                    os.write((getCurrentTestName() + "\n").getBytes(StandardCharsets.UTF_8));
-                }
-
-                Path file2 = clientFolder.resolve("file-2.txt");
-                String file2Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, file2);
-                Path file3 = clientFolder.resolve("file-3.txt");
-                String file3Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, file3);
-                try {
-                    sftp.rename(file2Path, file3Path);
-                    fail("Unxpected rename success of " + file2Path + " => " + file3Path);
-                } catch (SftpException e) {
-                    assertEquals("Mismatched status for failed rename of " + file2Path + " => " + file3Path,
-                            SftpConstants.SSH_FX_NO_SUCH_FILE, e.getStatus());
-                }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            Path file1 = clientFolder.resolve("file-1.txt");
+            String file1Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, file1);
+            try (OutputStream os = sftp.write(file1Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
+                os.write((getCurrentTestName() + "\n").getBytes(StandardCharsets.UTF_8));
+            }
 
-                try (OutputStream os = sftp.write(file2Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
-                    os.write("h".getBytes(StandardCharsets.UTF_8));
-                }
+            Path file2 = clientFolder.resolve("file-2.txt");
+            String file2Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, file2);
+            Path file3 = clientFolder.resolve("file-3.txt");
+            String file3Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, file3);
+            try {
+                sftp.rename(file2Path, file3Path);
+                fail("Unxpected rename success of " + file2Path + " => " + file3Path);
+            } catch (SftpException e) {
+                assertEquals("Mismatched status for failed rename of " + file2Path + " => " + file3Path,
+                        SftpConstants.SSH_FX_NO_SUCH_FILE, e.getStatus());
+            }
 
-                try {
-                    sftp.rename(file1Path, file2Path);
-                    fail("Unxpected rename success of " + file1Path + " => " + file2Path);
-                } catch (SftpException e) {
-                    assertEquals("Mismatched status for failed rename of " + file1Path + " => " + file2Path,
-                            SftpConstants.SSH_FX_FILE_ALREADY_EXISTS, e.getStatus());
-                }
+            try (OutputStream os = sftp.write(file2Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
+                os.write("h".getBytes(StandardCharsets.UTF_8));
+            }
 
-                sftp.rename(file1Path, file2Path, SftpClient.CopyMode.Overwrite);
+            try {
+                sftp.rename(file1Path, file2Path);
+                fail("Unxpected rename success of " + file1Path + " => " + file2Path);
+            } catch (SftpException e) {
+                assertEquals("Mismatched status for failed rename of " + file1Path + " => " + file2Path,
+                        SftpConstants.SSH_FX_FILE_ALREADY_EXISTS, e.getStatus());
             }
+
+            sftp.rename(file1Path, file2Path, SftpClient.CopyMode.Overwrite);
         }
     }
 
     @Test
     public void testServerExtensionsDeclarations() throws Exception {
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            Map<String, byte[]> extensions = sftp.getServerExtensions();
+            for (String name : new String[] {
+                    SftpConstants.EXT_NEWLINE, SftpConstants.EXT_VERSIONS,
+                    SftpConstants.EXT_VENDOR_ID, SftpConstants.EXT_ACL_SUPPORTED,
+                    SftpConstants.EXT_SUPPORTED, SftpConstants.EXT_SUPPORTED2
+            }) {
+                assertTrue("Missing extension=" + name, extensions.containsKey(name));
+            }
 
-            try (SftpClient sftp = createSftpClient(session)) {
-                Map<String, byte[]> extensions = sftp.getServerExtensions();
-                for (String name : new String[] {
-                        SftpConstants.EXT_NEWLINE, SftpConstants.EXT_VERSIONS,
-                        SftpConstants.EXT_VENDOR_ID, SftpConstants.EXT_ACL_SUPPORTED,
-                        SftpConstants.EXT_SUPPORTED, SftpConstants.EXT_SUPPORTED2
-                }) {
-                    assertTrue("Missing extension=" + name, extensions.containsKey(name));
+            Map<String, ?> data = ParserUtils.parse(extensions);
+            data.forEach((extName, extValue) -> {
+                outputDebugMessage("%s: %s", extName, extValue);
+                if (SftpConstants.EXT_SUPPORTED.equalsIgnoreCase(extName)) {
+                    assertSupportedExtensions(extName, ((Supported) extValue).extensionNames);
+                } else if (SftpConstants.EXT_SUPPORTED2.equalsIgnoreCase(extName)) {
+                    assertSupportedExtensions(extName, ((Supported2) extValue).extensionNames);
+                } else if (SftpConstants.EXT_ACL_SUPPORTED.equalsIgnoreCase(extName)) {
+                    assertSupportedAclCapabilities((AclCapabilities) extValue);
+                } else if (SftpConstants.EXT_VERSIONS.equalsIgnoreCase(extName)) {
+                    assertSupportedVersions((Versions) extValue);
+                } else if (SftpConstants.EXT_NEWLINE.equalsIgnoreCase(extName)) {
+                    assertNewlineValue((Newline) extValue);
                 }
+            });
 
-                Map<String, ?> data = ParserUtils.parse(extensions);
-                data.forEach((extName, extValue) -> {
-                    outputDebugMessage("%s: %s", extName, extValue);
-                    if (SftpConstants.EXT_SUPPORTED.equalsIgnoreCase(extName)) {
-                        assertSupportedExtensions(extName, ((Supported) extValue).extensionNames);
-                    } else if (SftpConstants.EXT_SUPPORTED2.equalsIgnoreCase(extName)) {
-                        assertSupportedExtensions(extName, ((Supported2) extValue).extensionNames);
-                    } else if (SftpConstants.EXT_ACL_SUPPORTED.equalsIgnoreCase(extName)) {
-                        assertSupportedAclCapabilities((AclCapabilities) extValue);
-                    } else if (SftpConstants.EXT_VERSIONS.equalsIgnoreCase(extName)) {
-                        assertSupportedVersions((Versions) extValue);
-                    } else if (SftpConstants.EXT_NEWLINE.equalsIgnoreCase(extName)) {
-                        assertNewlineValue((Newline) extValue);
-                    }
-                });
-
-                for (String extName : extensions.keySet()) {
-                    if (!data.containsKey(extName)) {
-                        outputDebugMessage("No parser for extension=%s", extName);
-                    }
+            for (String extName : extensions.keySet()) {
+                if (!data.containsKey(extName)) {
+                    outputDebugMessage("No parser for extension=%s", extName);
                 }
+            }
 
-                for (OpenSSHExtension expected : AbstractSftpSubsystemHelper.DEFAULT_OPEN_SSH_EXTENSIONS) {
-                    String name = expected.getName();
-                    Object value = data.get(name);
-                    assertNotNull("OpenSSH extension not declared: " + name, value);
+            for (OpenSSHExtension expected : AbstractSftpSubsystemHelper.DEFAULT_OPEN_SSH_EXTENSIONS) {
+                String name = expected.getName();
+                Object value = data.get(name);
+                assertNotNull("OpenSSH extension not declared: " + name, value);
 
-                    OpenSSHExtension actual = (OpenSSHExtension) value;
-                    assertEquals("Mismatched version for OpenSSH extension=" + name, expected.getVersion(),
-                            actual.getVersion());
-                }
+                OpenSSHExtension actual = (OpenSSHExtension) value;
+                assertEquals("Mismatched version for OpenSSH extension=" + name, expected.getVersion(),
+                        actual.getVersion());
+            }
 
-                for (BuiltinSftpClientExtensions type : BuiltinSftpClientExtensions.VALUES) {
-                    String extensionName = type.getName();
-                    boolean isOpenSSHExtension = extensionName.endsWith("@openssh.com");
-                    SftpClientExtension instance = sftp.getExtension(extensionName);
+            for (BuiltinSftpClientExtensions type : BuiltinSftpClientExtensions.VALUES) {
+                String extensionName = type.getName();
+                boolean isOpenSSHExtension = extensionName.endsWith("@openssh.com");
+                SftpClientExtension instance = sftp.getExtension(extensionName);
 
-                    assertNotNull("Extension not implemented:" + extensionName, instance);
-                    assertEquals("Mismatched instance name", extensionName, instance.getName());
+                assertNotNull("Extension not implemented:" + extensionName, instance);
+                assertEquals("Mismatched instance name", extensionName, instance.getName());
 
-                    if (instance.isSupported()) {
-                        if (isOpenSSHExtension) {
-                            assertTrue("Unlisted default OpenSSH extension: " + extensionName,
-                                    AbstractSftpSubsystemHelper.DEFAULT_OPEN_SSH_EXTENSIONS_NAMES.contains(extensionName));
-                        }
-                    } else {
-                        assertTrue("Unsupported non-OpenSSH extension: " + extensionName, isOpenSSHExtension);
-                        assertFalse("Unsupported default OpenSSH extension: " + extensionName,
+                if (instance.isSupported()) {
+                    if (isOpenSSHExtension) {
+                        assertTrue("Unlisted default OpenSSH extension: " + extensionName,
                                 AbstractSftpSubsystemHelper.DEFAULT_OPEN_SSH_EXTENSIONS_NAMES.contains(extensionName));
                     }
+                } else {
+                    assertTrue("Unsupported non-OpenSSH extension: " + extensionName, isOpenSSHExtension);
+                    assertFalse("Unsupported default OpenSSH extension: " + extensionName,
+                            AbstractSftpSubsystemHelper.DEFAULT_OPEN_SSH_EXTENSIONS_NAMES.contains(extensionName));
                 }
             }
         }
@@ -1351,15 +1283,10 @@ public class SftpTest extends AbstractSftpClientTestSupport {
             return value;
         };
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = SftpClientFactory.instance().createSftpClient(session, selector)) {
-                assertEquals("Mismatched negotiated version", selected.get(), sftp.getVersion());
-                testClient(client, sftp);
-            }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = SftpClientFactory.instance().createSftpClient(session, selector)) {
+            assertEquals("Mismatched negotiated version", selected.get(), sftp.getVersion());
+            testClient(client, sftp);
         }
     }
 
@@ -1369,11 +1296,7 @@ public class SftpTest extends AbstractSftpClientTestSupport {
         assertEquals("Mismatched subsystem factories count", 1, GenericUtils.size(factories));
 
         sshd.setSubsystemFactories(null);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
+        try (ClientSession session = createAuthenticatedClientSession()) {
             SftpModuleProperties.SFTP_CHANNEL_OPEN_TIMEOUT.set(session, Duration.ofSeconds(7L));
             try (SftpClient sftp = createSftpClient(session)) {
                 fail("Unexpected SFTP client creation success");
@@ -1574,14 +1497,9 @@ public class SftpTest extends AbstractSftpClientTestSupport {
     @Test // see SSHD-903
     public void testForcedVersionNegotiation() throws Exception {
         SftpModuleProperties.SFTP_VERSION.set(sshd, SftpConstants.SFTP_V3);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                assertEquals("Mismatched negotiated version", SftpConstants.SFTP_V3, sftp.getVersion());
-            }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            assertEquals("Mismatched negotiated version", SftpConstants.SFTP_V3, sftp.getVersion());
         }
     }
 
@@ -1629,28 +1547,23 @@ public class SftpTest extends AbstractSftpClientTestSupport {
 
         Path parentPath = targetPath.getParent();
         Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client"));
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                Path file = clientFolder.resolve("file.txt");
-                String filePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, file);
-                try (OutputStream os = sftp.write(filePath, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
-                    assertObjectInstanceOf(SftpOutputStreamAsync.class.getSimpleName(), SftpOutputStreamAsync.class, os);
-
-                    for (int index = 1; index <= 5; index++) {
-                        outputDebugMessage("%s - pre write flush attempt #%d", getCurrentTestName(), index);
-                        os.flush();
-                    }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            Path file = clientFolder.resolve("file.txt");
+            String filePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, file);
+            try (OutputStream os = sftp.write(filePath, SftpClient.MIN_WRITE_BUFFER_SIZE)) {
+                assertObjectInstanceOf(SftpOutputStreamAsync.class.getSimpleName(), SftpOutputStreamAsync.class, os);
+
+                for (int index = 1; index <= 5; index++) {
+                    outputDebugMessage("%s - pre write flush attempt #%d", getCurrentTestName(), index);
+                    os.flush();
+                }
 
-                    os.write((getCurrentTestName() + "\n").getBytes(StandardCharsets.UTF_8));
+                os.write((getCurrentTestName() + "\n").getBytes(StandardCharsets.UTF_8));
 
-                    for (int index = 1; index <= 5; index++) {
-                        outputDebugMessage("%s - post write flush attempt #%d", getCurrentTestName(), index);
-                        os.flush();
-                    }
+                for (int index = 1; index <= 5; index++) {
+                    outputDebugMessage("%s - post write flush attempt #%d", getCurrentTestName(), index);
+                    os.flush();
                 }
             }
         }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTransferTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTransferTest.java
index 1402071..3a24d68 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTransferTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTransferTest.java
@@ -72,7 +72,7 @@ public class SftpTransferTest extends AbstractSftpClientTestSupport {
             }
         }
 
-        try (ClientSession session = createClientSession();
+        try (ClientSession session = createAuthenticatedClientSession();
              SftpFileSystem fs = SftpClientFactory.instance().createSftpFileSystem(session)) {
             if (bufferSize > 0) {
                 fs.setReadBufferSize(bufferSize);
@@ -101,19 +101,6 @@ public class SftpTransferTest extends AbstractSftpClientTestSupport {
         }
     }
 
-    private ClientSession createClientSession() throws IOException {
-        ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession();
-        try {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-            return session;
-        } catch (IOException e) {
-            session.close();
-            throw e;
-        }
-    }
-
     private static void assertSameContent(Path path, Path path2) throws IOException {
         long l1 = Files.size(path);
         long l2 = Files.size(path2);
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java
index b9eb048..8e04aa1 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java
@@ -124,32 +124,21 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
 
         Path parentPath = targetPath.getParent();
         String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-            try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
-                try (OutputStream out = sftp.write(remotePath, OpenMode.Create, OpenMode.Write)) {
-                    out.write(getCurrentTestName().getBytes(StandardCharsets.UTF_8));
-                }
-                assertTrue("File should exist on disk: " + lclFile, Files.exists(lclFile));
-                sftp.remove(remotePath);
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+            try (OutputStream out = sftp.write(remotePath, OpenMode.Create, OpenMode.Write)) {
+                out.write(getCurrentTestName().getBytes(StandardCharsets.UTF_8));
             }
+            assertTrue("File should exist on disk: " + lclFile, Files.exists(lclFile));
+            sftp.remove(remotePath);
         }
     }
 
     @Test
     public void testSftpVersionSelector() throws Exception {
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
-                assertEquals("Mismatched negotiated version", getTestedVersion(), sftp.getVersion());
-            }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+            assertEquals("Mismatched negotiated version", getTestedVersion(), sftp.getVersion());
         }
     }
 
@@ -163,32 +152,26 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
         Files.write(lclFile, getClass().getName().getBytes(StandardCharsets.UTF_8));
         Path parentPath = targetPath.getParent();
         String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
-                Attributes attrs = sftp.lstat(remotePath);
-                long expectedSeconds = TimeUnit.SECONDS.convert(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1L),
-                        TimeUnit.MILLISECONDS);
-                attrs.getFlags().clear();
-                attrs.modifyTime(expectedSeconds);
-                sftp.setStat(remotePath, attrs);
-
-                attrs = sftp.lstat(remotePath);
-                long actualSeconds = attrs.getModifyTime().to(TimeUnit.SECONDS);
-                // The NTFS file system delays updates to the last access time for a file by up to 1 hour after the last
-                // access
-                if (expectedSeconds != actualSeconds) {
-                    System.err.append("Mismatched last modified time for ").append(lclFile.toString())
-                            .append(" - expected=").append(String.valueOf(expectedSeconds))
-                            .append('[').append(new Date(TimeUnit.SECONDS.toMillis(expectedSeconds)).toString()).append(']')
-                            .append(", actual=").append(String.valueOf(actualSeconds))
-                            .append('[').append(new Date(TimeUnit.SECONDS.toMillis(actualSeconds)).toString()).append(']')
-                            .println();
-                }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+            Attributes attrs = sftp.lstat(remotePath);
+            long expectedSeconds = TimeUnit.SECONDS.convert(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1L),
+                    TimeUnit.MILLISECONDS);
+            attrs.getFlags().clear();
+            attrs.modifyTime(expectedSeconds);
+            sftp.setStat(remotePath, attrs);
+
+            attrs = sftp.lstat(remotePath);
+            long actualSeconds = attrs.getModifyTime().to(TimeUnit.SECONDS);
+            // The NTFS file system delays updates to the last access time for a file by up to 1 hour after the last
+            // access
+            if (expectedSeconds != actualSeconds) {
+                System.err.append("Mismatched last modified time for ").append(lclFile.toString())
+                        .append(" - expected=").append(String.valueOf(expectedSeconds))
+                        .append('[').append(new Date(TimeUnit.SECONDS.toMillis(expectedSeconds)).toString()).append(']')
+                        .append(", actual=").append(String.valueOf(actualSeconds))
+                        .append('[').append(new Date(TimeUnit.SECONDS.toMillis(actualSeconds)).toString()).append(']')
+                        .println();
             }
         }
     }
@@ -207,27 +190,21 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
 
         Path parentPath = targetPath.getParent();
         String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclSftp);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
-                for (DirEntry entry : sftp.readDir(remotePath)) {
-                    String fileName = entry.getFilename();
-                    if (".".equals(fileName) || "..".equals(fileName)) {
-                        continue;
-                    }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+            for (DirEntry entry : sftp.readDir(remotePath)) {
+                String fileName = entry.getFilename();
+                if (".".equals(fileName) || "..".equals(fileName)) {
+                    continue;
+                }
 
-                    Attributes attrs = validateSftpFileTypeAndPermissions(fileName, getTestedVersion(), entry.getAttributes());
-                    if (subFolderName.equals(fileName)) {
-                        assertEquals("Mismatched sub-folder type", SftpConstants.SSH_FILEXFER_TYPE_DIRECTORY, attrs.getType());
-                        assertTrue("Sub-folder not marked as directory", attrs.isDirectory());
-                    } else if (lclFileName.equals(fileName)) {
-                        assertEquals("Mismatched sub-file type", SftpConstants.SSH_FILEXFER_TYPE_REGULAR, attrs.getType());
-                        assertTrue("Sub-folder not marked as directory", attrs.isRegularFile());
-                    }
+                Attributes attrs = validateSftpFileTypeAndPermissions(fileName, getTestedVersion(), entry.getAttributes());
+                if (subFolderName.equals(fileName)) {
+                    assertEquals("Mismatched sub-folder type", SftpConstants.SSH_FILEXFER_TYPE_DIRECTORY, attrs.getType());
+                    assertTrue("Sub-folder not marked as directory", attrs.isDirectory());
+                } else if (lclFileName.equals(fileName)) {
+                    assertEquals("Mismatched sub-file type", SftpConstants.SSH_FILEXFER_TYPE_REGULAR, attrs.getType());
+                    assertTrue("Sub-folder not marked as directory", attrs.isRegularFile());
                 }
             }
         }
@@ -328,33 +305,27 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
 
         List<SubsystemFactory> factories = sshd.getSubsystemFactories();
         sshd.setSubsystemFactories(Collections.singletonList(factory));
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
-                for (DirEntry entry : sftp.readDir(remotePath)) {
-                    String fileName = entry.getFilename();
-                    if (".".equals(fileName) || "..".equals(fileName)) {
-                        continue;
-                    }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+            for (DirEntry entry : sftp.readDir(remotePath)) {
+                String fileName = entry.getFilename();
+                if (".".equals(fileName) || "..".equals(fileName)) {
+                    continue;
+                }
 
-                    Attributes attrs = validateSftpFileTypeAndPermissions(fileName, getTestedVersion(), entry.getAttributes());
-                    List<AclEntry> aclActual = attrs.getAcl();
-                    if (getTestedVersion() == SftpConstants.SFTP_V3) {
-                        assertNull("Unexpected ACL for entry=" + fileName, aclActual);
-                    } else {
-                        assertListEquals("Mismatched ACL for entry=" + fileName, aclExpected, aclActual);
-                    }
+                Attributes attrs = validateSftpFileTypeAndPermissions(fileName, getTestedVersion(), entry.getAttributes());
+                List<AclEntry> aclActual = attrs.getAcl();
+                if (getTestedVersion() == SftpConstants.SFTP_V3) {
+                    assertNull("Unexpected ACL for entry=" + fileName, aclActual);
+                } else {
+                    assertListEquals("Mismatched ACL for entry=" + fileName, aclExpected, aclActual);
+                }
 
-                    attrs.getFlags().clear();
-                    attrs.setAcl(aclExpected);
-                    sftp.setStat(remotePath + "/" + fileName, attrs);
-                    if (getTestedVersion() > SftpConstants.SFTP_V3) {
-                        numInvoked++;
-                    }
+                attrs.getFlags().clear();
+                attrs.setAcl(aclExpected);
+                sftp.setStat(remotePath + "/" + fileName, attrs);
+                if (getTestedVersion() > SftpConstants.SFTP_V3) {
+                    numInvoked++;
                 }
             }
         } finally {
@@ -456,27 +427,21 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
 
         List<SubsystemFactory> factories = sshd.getSubsystemFactories();
         sshd.setSubsystemFactories(Collections.singletonList(factory));
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
-                for (DirEntry entry : sftp.readDir(remotePath)) {
-                    String fileName = entry.getFilename();
-                    if (".".equals(fileName) || "..".equals(fileName)) {
-                        continue;
-                    }
-
-                    Attributes attrs = validateSftpFileTypeAndPermissions(fileName, getTestedVersion(), entry.getAttributes());
-                    Map<String, byte[]> actExtensions = attrs.getExtensions();
-                    assertExtensionsMapEquals("dirEntry=" + fileName, expExtensions, actExtensions);
-                    attrs.getFlags().clear();
-                    attrs.setStringExtensions(expExtensions);
-                    sftp.setStat(remotePath + "/" + fileName, attrs);
-                    numInvoked++;
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+            for (DirEntry entry : sftp.readDir(remotePath)) {
+                String fileName = entry.getFilename();
+                if (".".equals(fileName) || "..".equals(fileName)) {
+                    continue;
                 }
+
+                Attributes attrs = validateSftpFileTypeAndPermissions(fileName, getTestedVersion(), entry.getAttributes());
+                Map<String, byte[]> actExtensions = attrs.getExtensions();
+                assertExtensionsMapEquals("dirEntry=" + fileName, expExtensions, actExtensions);
+                attrs.getFlags().clear();
+                attrs.setStringExtensions(expExtensions);
+                sftp.setStat(remotePath + "/" + fileName, attrs);
+                numInvoked++;
             }
         } finally {
             sshd.setSubsystemFactories(factories);
@@ -487,43 +452,37 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
 
     @Test // see SSHD-623
     public void testEndOfListIndicator() throws Exception {
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT)
-                .getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
-                AtomicReference<Boolean> eolIndicator = new AtomicReference<>();
-                int version = sftp.getVersion();
-                Path targetPath = detectTargetFolder();
-                Path parentPath = targetPath.getParent();
-                String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, targetPath);
-
-                try (CloseableHandle handle = sftp.openDir(remotePath)) {
-                    List<DirEntry> entries = sftp.readDir(handle, eolIndicator);
-                    for (int index = 1; entries != null; entries = sftp.readDir(handle, eolIndicator), index++) {
-                        Boolean value = eolIndicator.get();
-                        if (version < SftpConstants.SFTP_V6) {
-                            assertNull("Unexpected indicator value at iteration #" + index, value);
-                        } else {
-                            assertNotNull("No indicator returned at iteration #" + index, value);
-                            if (value) {
-                                break;
-                            }
-                        }
-                        eolIndicator.set(null); // make sure starting fresh
-                    }
-
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+            AtomicReference<Boolean> eolIndicator = new AtomicReference<>();
+            int version = sftp.getVersion();
+            Path targetPath = detectTargetFolder();
+            Path parentPath = targetPath.getParent();
+            String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, targetPath);
+
+            try (CloseableHandle handle = sftp.openDir(remotePath)) {
+                List<DirEntry> entries = sftp.readDir(handle, eolIndicator);
+                for (int index = 1; entries != null; entries = sftp.readDir(handle, eolIndicator), index++) {
                     Boolean value = eolIndicator.get();
                     if (version < SftpConstants.SFTP_V6) {
-                        assertNull("Unexpected end-of-list indication received at end of entries", value);
-                        assertNull("Unexpected no last entries indication", entries);
+                        assertNull("Unexpected indicator value at iteration #" + index, value);
                     } else {
-                        assertNotNull("No end-of-list indication received at end of entries", value);
-                        assertNotNull("No last received entries", entries);
-                        assertTrue("Bad end-of-list value", value);
+                        assertNotNull("No indicator returned at iteration #" + index, value);
+                        if (value) {
+                            break;
+                        }
                     }
+                    eolIndicator.set(null); // make sure starting fresh
+                }
+
+                Boolean value = eolIndicator.get();
+                if (version < SftpConstants.SFTP_V6) {
+                    assertNull("Unexpected end-of-list indication received at end of entries", value);
+                    assertNull("Unexpected no last entries indication", entries);
+                } else {
+                    assertNotNull("No end-of-list indication received at end of entries", value);
+                    assertNotNull("No last received entries", entries);
+                    assertTrue("Bad end-of-list value", value);
                 }
             }
         }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/UnsupportedExtensionTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/UnsupportedExtensionTest.java
index 3862bd6..3aaae5b 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/UnsupportedExtensionTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/UnsupportedExtensionTest.java
@@ -41,29 +41,24 @@ public class UnsupportedExtensionTest extends AbstractSftpClientTestSupport {
 
     @Test // see SSHD-890
     public void testUnsupportedExtension() throws IOException {
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftpClient = createSftpClient(session)) {
+            String opcode = getCurrentTestName();
+            Buffer buffer = new ByteArrayBuffer(Integer.BYTES + GenericUtils.length(opcode) + Byte.SIZE, false);
+            buffer.putString(opcode);
 
-            try (SftpClient sftpClient = createSftpClient(session)) {
-                String opcode = getCurrentTestName();
-                Buffer buffer = new ByteArrayBuffer(Integer.BYTES + GenericUtils.length(opcode) + Byte.SIZE, false);
-                buffer.putString(opcode);
+            assertObjectInstanceOf("Not a raw SFTP client", RawSftpClient.class, sftpClient);
+            RawSftpClient sftp = (RawSftpClient) sftpClient;
+            int cmd = sftp.send(SftpConstants.SSH_FXP_EXTENDED, buffer);
+            Buffer responseBuffer = sftp.receive(cmd);
 
-                assertObjectInstanceOf("Not a raw SFTP client", RawSftpClient.class, sftpClient);
-                RawSftpClient sftp = (RawSftpClient) sftpClient;
-                int cmd = sftp.send(SftpConstants.SSH_FXP_EXTENDED, buffer);
-                Buffer responseBuffer = sftp.receive(cmd);
+            responseBuffer.getInt(); // Ignoring length
+            int type = responseBuffer.getUByte();
+            responseBuffer.getInt(); // Ignoring message ID
+            int substatus = responseBuffer.getInt();
 
-                responseBuffer.getInt(); // Ignoring length
-                int type = responseBuffer.getUByte();
-                responseBuffer.getInt(); // Ignoring message ID
-                int substatus = responseBuffer.getInt();
-
-                assertEquals("Type is not STATUS", SftpConstants.SSH_FXP_STATUS, type);
-                assertEquals("Sub-Type is not UNSUPPORTED", SftpConstants.SSH_FX_OP_UNSUPPORTED, substatus);
-            }
+            assertEquals("Type is not STATUS", SftpConstants.SSH_FXP_STATUS, type);
+            assertEquals("Sub-Type is not UNSUPPORTED", SftpConstants.SSH_FX_OP_UNSUPPORTED, substatus);
         }
     }
 }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractCheckFileExtensionTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractCheckFileExtensionTest.java
index 2317977..af7b3ec 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractCheckFileExtensionTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractCheckFileExtensionTest.java
@@ -175,37 +175,32 @@ public class AbstractCheckFileExtensionTest extends AbstractSftpClientTestSuppor
         Path parentPath = targetPath.getParent();
         String srcPath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile);
         String srcFolder = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile.getParent());
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            CheckFileNameExtension file = assertExtensionCreated(sftp, CheckFileNameExtension.class);
+            try {
+                Map.Entry<String, ?> result = file.checkFileName(srcFolder, algorithms, 0L, 0L, hashBlockSize);
+                fail("Unexpected success to hash folder=" + srcFolder + ": " + result.getKey());
+            } catch (IOException e) { // expected - not allowed to hash a folder
+                assertTrue("Not an SftpException", e instanceof SftpException);
+            }
 
-            try (SftpClient sftp = createSftpClient(session)) {
-                CheckFileNameExtension file = assertExtensionCreated(sftp, CheckFileNameExtension.class);
+            CheckFileHandleExtension hndl = assertExtensionCreated(sftp, CheckFileHandleExtension.class);
+            try (CloseableHandle dirHandle = sftp.openDir(srcFolder)) {
                 try {
-                    Map.Entry<String, ?> result = file.checkFileName(srcFolder, algorithms, 0L, 0L, hashBlockSize);
-                    fail("Unexpected success to hash folder=" + srcFolder + ": " + result.getKey());
+                    Map.Entry<String, ?> result = hndl.checkFileHandle(dirHandle, algorithms, 0L, 0L, hashBlockSize);
+                    fail("Unexpected handle success on folder=" + srcFolder + ": " + result.getKey());
                 } catch (IOException e) { // expected - not allowed to hash a folder
                     assertTrue("Not an SftpException", e instanceof SftpException);
                 }
+            }
 
-                CheckFileHandleExtension hndl = assertExtensionCreated(sftp, CheckFileHandleExtension.class);
-                try (CloseableHandle dirHandle = sftp.openDir(srcFolder)) {
-                    try {
-                        Map.Entry<String, ?> result = hndl.checkFileHandle(dirHandle, algorithms, 0L, 0L, hashBlockSize);
-                        fail("Unexpected handle success on folder=" + srcFolder + ": " + result.getKey());
-                    } catch (IOException e) { // expected - not allowed to hash a folder
-                        assertTrue("Not an SftpException", e instanceof SftpException);
-                    }
-                }
-
-                String hashAlgo = algorithms.get(0);
-                validateHashResult(file, file.checkFileName(srcPath, algorithms, 0L, 0L, hashBlockSize), hashAlgo,
+            String hashAlgo = algorithms.get(0);
+            validateHashResult(file, file.checkFileName(srcPath, algorithms, 0L, 0L, hashBlockSize), hashAlgo,
+                    expectedHash);
+            try (CloseableHandle fileHandle = sftp.open(srcPath, SftpClient.OpenMode.Read)) {
+                validateHashResult(hndl, hndl.checkFileHandle(fileHandle, algorithms, 0L, 0L, hashBlockSize), hashAlgo,
                         expectedHash);
-                try (CloseableHandle fileHandle = sftp.open(srcPath, SftpClient.OpenMode.Read)) {
-                    validateHashResult(hndl, hndl.checkFileHandle(fileHandle, algorithms, 0L, 0L, hashBlockSize), hashAlgo,
-                            expectedHash);
-                }
             }
         }
     }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractMD5HashExtensionTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractMD5HashExtensionTest.java
index 9b6d10e..aa70070 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractMD5HashExtensionTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractMD5HashExtensionTest.java
@@ -133,42 +133,37 @@ public class AbstractMD5HashExtensionTest extends AbstractSftpClientTestSupport
         Path parentPath = targetPath.getParent();
         String srcPath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile);
         String srcFolder = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile.getParent());
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            MD5FileExtension file = assertExtensionCreated(sftp, MD5FileExtension.class);
+            try {
+                byte[] actual = file.getHash(srcFolder, 0L, 0L, quickHash);
+                fail("Unexpected file success on folder=" + srcFolder + ": " + BufferUtils.toHex(':', actual));
+            } catch (IOException e) { // expected - not allowed to hash a folder
+                assertTrue("Not an SftpException for file hash on " + srcFolder, e instanceof SftpException);
+            }
 
-            try (SftpClient sftp = createSftpClient(session)) {
-                MD5FileExtension file = assertExtensionCreated(sftp, MD5FileExtension.class);
+            MD5HandleExtension hndl = assertExtensionCreated(sftp, MD5HandleExtension.class);
+            try (CloseableHandle dirHandle = sftp.openDir(srcFolder)) {
                 try {
-                    byte[] actual = file.getHash(srcFolder, 0L, 0L, quickHash);
-                    fail("Unexpected file success on folder=" + srcFolder + ": " + BufferUtils.toHex(':', actual));
+                    byte[] actual = hndl.getHash(dirHandle, 0L, 0L, quickHash);
+                    fail("Unexpected handle success on folder=" + srcFolder + ": " + BufferUtils.toHex(':', actual));
                 } catch (IOException e) { // expected - not allowed to hash a folder
-                    assertTrue("Not an SftpException for file hash on " + srcFolder, e instanceof SftpException);
-                }
-
-                MD5HandleExtension hndl = assertExtensionCreated(sftp, MD5HandleExtension.class);
-                try (CloseableHandle dirHandle = sftp.openDir(srcFolder)) {
-                    try {
-                        byte[] actual = hndl.getHash(dirHandle, 0L, 0L, quickHash);
-                        fail("Unexpected handle success on folder=" + srcFolder + ": " + BufferUtils.toHex(':', actual));
-                    } catch (IOException e) { // expected - not allowed to hash a folder
-                        assertTrue("Not an SftpException for handle hash on " + srcFolder, e instanceof SftpException);
-                    }
+                    assertTrue("Not an SftpException for handle hash on " + srcFolder, e instanceof SftpException);
                 }
+            }
 
-                try (CloseableHandle fileHandle = sftp.open(srcPath, SftpClient.OpenMode.Read)) {
-                    for (byte[] qh : new byte[][] { GenericUtils.EMPTY_BYTE_ARRAY, quickHash }) {
-                        for (boolean useFile : new boolean[] { true, false }) {
-                            byte[] actualHash
-                                    = useFile ? file.getHash(srcPath, 0L, 0L, qh) : hndl.getHash(fileHandle, 0L, 0L, qh);
-                            String type = useFile ? file.getClass().getSimpleName() : hndl.getClass().getSimpleName();
-                            if (!Arrays.equals(expectedHash, actualHash)) {
-                                fail("Mismatched hash for quick=" + BufferUtils.toHex(':', qh)
-                                     + " using " + type + " on " + srcFile
-                                     + ": expected=" + BufferUtils.toHex(':', expectedHash)
-                                     + ", actual=" + BufferUtils.toHex(':', actualHash));
-                            }
+            try (CloseableHandle fileHandle = sftp.open(srcPath, SftpClient.OpenMode.Read)) {
+                for (byte[] qh : new byte[][] { GenericUtils.EMPTY_BYTE_ARRAY, quickHash }) {
+                    for (boolean useFile : new boolean[] { true, false }) {
+                        byte[] actualHash
+                                = useFile ? file.getHash(srcPath, 0L, 0L, qh) : hndl.getHash(fileHandle, 0L, 0L, qh);
+                        String type = useFile ? file.getClass().getSimpleName() : hndl.getClass().getSimpleName();
+                        if (!Arrays.equals(expectedHash, actualHash)) {
+                            fail("Mismatched hash for quick=" + BufferUtils.toHex(':', qh)
+                                 + " using " + type + " on " + srcFile
+                                 + ": expected=" + BufferUtils.toHex(':', expectedHash)
+                                 + ", actual=" + BufferUtils.toHex(':', actualHash));
                         }
                     }
                 }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyDataExtensionImplTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyDataExtensionImplTest.java
index 9d46d6f..8301d3c 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyDataExtensionImplTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyDataExtensionImplTest.java
@@ -159,17 +159,12 @@ public class CopyDataExtensionImplTest extends AbstractSftpClientTestSupport {
             }
         }
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                CopyDataExtension ext = assertExtensionCreated(sftp, CopyDataExtension.class);
-                try (CloseableHandle readHandle = sftp.open(srcPath, SftpClient.OpenMode.Read);
-                     CloseableHandle writeHandle = sftp.open(dstPath, SftpClient.OpenMode.Write, SftpClient.OpenMode.Create)) {
-                    ext.copyData(readHandle, readOffset, readLength, writeHandle, writeOffset);
-                }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            CopyDataExtension ext = assertExtensionCreated(sftp, CopyDataExtension.class);
+            try (CloseableHandle readHandle = sftp.open(srcPath, SftpClient.OpenMode.Read);
+                 CloseableHandle writeHandle = sftp.open(dstPath, SftpClient.OpenMode.Write, SftpClient.OpenMode.Create)) {
+                ext.copyData(readHandle, readOffset, readLength, writeHandle, writeOffset);
             }
         }
 
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyFileExtensionImplTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyFileExtensionImplTest.java
index 9d3ddbb..08dbe67 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyFileExtensionImplTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyFileExtensionImplTest.java
@@ -71,26 +71,21 @@ public class CopyFileExtensionImplTest extends AbstractSftpClientTestSupport {
         LinkOption[] options = IoUtils.getLinkOptions(true);
         assertFalse("Destination file unexpectedly exists", Files.exists(dstFile, options));
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            CopyFileExtension ext = assertExtensionCreated(sftp, CopyFileExtension.class);
+            ext.copyFile(srcPath, dstPath, false);
+            assertTrue("Source file not preserved", Files.exists(srcFile, options));
+            assertTrue("Destination file not created", Files.exists(dstFile, options));
 
-            try (SftpClient sftp = createSftpClient(session)) {
-                CopyFileExtension ext = assertExtensionCreated(sftp, CopyFileExtension.class);
-                ext.copyFile(srcPath, dstPath, false);
-                assertTrue("Source file not preserved", Files.exists(srcFile, options));
-                assertTrue("Destination file not created", Files.exists(dstFile, options));
-
-                byte[] actual = Files.readAllBytes(dstFile);
-                assertArrayEquals("Mismatched copied data", data, actual);
+            byte[] actual = Files.readAllBytes(dstFile);
+            assertArrayEquals("Mismatched copied data", data, actual);
 
-                try {
-                    ext.copyFile(srcPath, dstPath, false);
-                    fail("Unexpected success to overwrite existing destination: " + dstFile);
-                } catch (IOException e) {
-                    assertTrue("Not an SftpException", e instanceof SftpException);
-                }
+            try {
+                ext.copyFile(srcPath, dstPath, false);
+                fail("Unexpected success to overwrite existing destination: " + dstFile);
+            } catch (IOException e) {
+                assertTrue("Not an SftpException", e instanceof SftpException);
             }
         }
     }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/SpaceAvailableExtensionImplTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/SpaceAvailableExtensionImplTest.java
index 7b8fed3..1158c09 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/SpaceAvailableExtensionImplTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/SpaceAvailableExtensionImplTest.java
@@ -88,16 +88,11 @@ public class SpaceAvailableExtensionImplTest extends AbstractSftpClientTestSuppo
             }
         }));
 
-        try (ClientSession session
-                = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                SpaceAvailableExtension ext = assertExtensionCreated(sftp, SpaceAvailableExtension.class);
-                SpaceAvailableExtensionInfo actual = ext.available(queryPath);
-                assertEquals("Mismatched information", expected, actual);
-            }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            SpaceAvailableExtension ext = assertExtensionCreated(sftp, SpaceAvailableExtension.class);
+            SpaceAvailableExtensionInfo actual = ext.available(queryPath);
+            assertEquals("Mismatched information", expected, actual);
         } finally {
             sshd.setSubsystemFactories(factories);
         }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java
index 022deca..639ff89 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java
@@ -31,7 +31,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
-import org.apache.sshd.client.SshClient;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
@@ -82,20 +81,15 @@ public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport {
 
         Path parentPath = targetPath.getParent();
         String srcPath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                OpenSSHFsyncExtension fsync = assertExtensionCreated(sftp, OpenSSHFsyncExtension.class);
-                try (CloseableHandle fileHandle = sftp.open(srcPath, SftpClient.OpenMode.Write, SftpClient.OpenMode.Create)) {
-                    sftp.write(fileHandle, 0L, expected);
-                    fsync.fsync(fileHandle);
-
-                    byte[] actual = Files.readAllBytes(srcFile);
-                    assertArrayEquals("Mismatched written data", expected, actual);
-                }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            OpenSSHFsyncExtension fsync = assertExtensionCreated(sftp, OpenSSHFsyncExtension.class);
+            try (CloseableHandle fileHandle = sftp.open(srcPath, SftpClient.OpenMode.Write, SftpClient.OpenMode.Create)) {
+                sftp.write(fileHandle, 0L, expected);
+                fsync.fsync(fileHandle);
+
+                byte[] actual = Files.readAllBytes(srcFile);
+                assertArrayEquals("Mismatched written data", expected, actual);
             }
         }
     }
@@ -170,29 +164,20 @@ public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport {
             }
         }));
 
-        try (SshClient client = setupTestClient()) {
-            client.start();
-
-            try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                    .verify(CONNECT_TIMEOUT).getSession()) {
-                session.addPasswordIdentity(getCurrentTestName());
-                session.auth().verify(AUTH_TIMEOUT);
-
-                try (SftpClient sftp = createSftpClient(session)) {
-                    OpenSSHStatPathExtension pathStat = assertExtensionCreated(sftp, OpenSSHStatPathExtension.class);
-                    OpenSSHStatExtensionInfo actual = pathStat.stat(srcPath);
-                    String invokedExtension = extensionHolder.getAndSet(null);
-                    assertEquals("Mismatched invoked extension", pathStat.getName(), invokedExtension);
-                    assertOpenSSHStatExtensionInfoEquals(invokedExtension, expected, actual);
-
-                    try (CloseableHandle handle = sftp.open(srcPath)) {
-                        OpenSSHStatHandleExtension handleStat = assertExtensionCreated(sftp, OpenSSHStatHandleExtension.class);
-                        actual = handleStat.stat(handle);
-                        invokedExtension = extensionHolder.getAndSet(null);
-                        assertEquals("Mismatched invoked extension", handleStat.getName(), invokedExtension);
-                        assertOpenSSHStatExtensionInfoEquals(invokedExtension, expected, actual);
-                    }
-                }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            OpenSSHStatPathExtension pathStat = assertExtensionCreated(sftp, OpenSSHStatPathExtension.class);
+            OpenSSHStatExtensionInfo actual = pathStat.stat(srcPath);
+            String invokedExtension = extensionHolder.getAndSet(null);
+            assertEquals("Mismatched invoked extension", pathStat.getName(), invokedExtension);
+            assertOpenSSHStatExtensionInfoEquals(invokedExtension, expected, actual);
+
+            try (CloseableHandle handle = sftp.open(srcPath)) {
+                OpenSSHStatHandleExtension handleStat = assertExtensionCreated(sftp, OpenSSHStatHandleExtension.class);
+                actual = handleStat.stat(handle);
+                invokedExtension = extensionHolder.getAndSet(null);
+                assertEquals("Mismatched invoked extension", handleStat.getName(), invokedExtension);
+                assertOpenSSHStatExtensionInfoEquals(invokedExtension, expected, actual);
             }
         }
     }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java
index 612afc8..d238ddc 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java
@@ -19,21 +19,25 @@
 
 package org.apache.sshd.sftp.client.fs;
 
+import java.io.File;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
-import java.util.function.BiPredicate;
 
+import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.io.DirectoryScanner;
+import org.apache.sshd.common.util.io.PathUtils;
+import org.apache.sshd.sftp.client.SftpClient;
+import org.apache.sshd.sftp.client.SftpClient.Attributes;
+import org.apache.sshd.sftp.client.SftpClient.DirEntry;
+import org.apache.sshd.sftp.client.fs.SftpClientDirectoryScanner.ScanDirEntry;
 import org.apache.sshd.util.test.CommonTestSupportUtils;
 import org.junit.Before;
 import org.junit.FixMethodOrder;
@@ -45,12 +49,6 @@ import org.junit.runners.MethodSorters;
  */
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class SftpDirectoryScannersTest extends AbstractSftpFilesSystemSupport {
-    private static final BiPredicate<Path, Path> BY_FILE_NAME = (p1, p2) -> {
-        String n1 = Objects.toString(p1.getFileName());
-        String n2 = Objects.toString(p2.getFileName());
-        return Objects.equals(n1, n2);
-    };
-
     public SftpDirectoryScannersTest() throws IOException {
         super();
     }
@@ -66,27 +64,68 @@ public class SftpDirectoryScannersTest extends AbstractSftpFilesSystemSupport {
     }
 
     @Test
-    public void testSftpDirectoryScannerFileSuffixMatching() throws IOException {
+    public void testSftpPathDirectoryScannerFileSuffixMatching() throws IOException {
         testSftpPathDirectoryScanner(setupFileSuffixMatching(), "*.txt");
     }
 
-    private void testSftpPathDirectoryScanner(
-            Map.Entry<String, List<Path>> setup, String pattern)
-            throws IOException {
-        List<Path> expected = setup.getValue();
+    private void testSftpPathDirectoryScanner(SetupDetails setup, String pattern) throws IOException {
+        List<Path> expected = setup.getExpected();
         List<Path> actual;
         try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) {
-            String remDirPath = setup.getKey();
+            String remDirPath = setup.getRemoteFilePath();
             Path basedir = fs.getPath(remDirPath);
             DirectoryScanner ds = new SftpPathDirectoryScanner(basedir, pattern);
             actual = ds.scan(() -> new ArrayList<>(expected.size()));
         }
         Collections.sort(actual);
 
-        assertListEquals(getCurrentTestName(), expected, actual, BY_FILE_NAME);
+        assertListEquals(getCurrentTestName(), expected, actual, PathUtils.EQ_CASE_SENSITIVE_FILENAME);
+    }
+
+    @Test
+    public void testSftpClientDirectoryScannerDeepScanning() throws IOException {
+        testSftpClientDirectoryScanner(setupDeepScanning(), "**/*");
+    }
+
+    @Test
+    public void testSftpClientDirectoryScannerFileSuffixMatching() throws IOException {
+        testSftpClientDirectoryScanner(setupFileSuffixMatching(), "*.txt");
+    }
+
+    private void testSftpClientDirectoryScanner(SetupDetails setup, String pattern) throws IOException {
+        List<Path> expected = setup.getExpected();
+        String remRoot = setup.getRemoteFilePath();
+        List<ScanDirEntry> actual;
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            SftpClientDirectoryScanner ds = new SftpClientDirectoryScanner(remRoot, pattern);
+            actual = ds.scan(sftp, () -> new ArrayList<>(expected.size()));
+        }
+
+        assertEquals("Mismatched result size", expected.size(), actual.size());
+
+        Collections.sort(expected, PathUtils.BY_CASE_INSENSITIVE_FILENAME);
+        Collections.sort(actual, DirEntry.BY_CASE_SENSITIVE_FILENAME);
+
+        Path lclRoot = setup.getRootDir();
+        for (int index = 0, count = expected.size(); index < count; index++) {
+            Path lclPath = expected.get(index);
+            ScanDirEntry remEntry = actual.get(index);
+            String filename = remEntry.getFilename();
+            assertEquals("Mismatched name", Objects.toString(lclPath.getFileName()), filename);
+
+            Path relPath = lclRoot.relativize(lclPath);
+            String lclRelative = Objects.toString(relPath).replace(File.separatorChar, '/');
+            assertEquals("Mismatched relative path", lclRelative, remEntry.getRelativePath());
+
+            Attributes attrs = remEntry.getAttributes();
+            assertEquals("Mismatched directory indicator for " + filename, Files.isDirectory(lclPath), attrs.isDirectory());
+            assertEquals("Mismatched regular file indicator for " + filename, Files.isRegularFile(lclPath),
+                    attrs.isRegularFile());
+        }
     }
 
-    private Map.Entry<String, List<Path>> setupDeepScanning() throws IOException {
+    private SetupDetails setupDeepScanning() throws IOException {
         Path targetPath = detectTargetFolder();
         Path rootDir = CommonTestSupportUtils.resolve(targetPath,
                 TEMP_SUBFOLDER_NAME, getClass().getSimpleName(), getCurrentTestName());
@@ -107,11 +146,10 @@ public class SftpDirectoryScannersTest extends AbstractSftpFilesSystemSupport {
 
         Path parentPath = targetPath.getParent();
         String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, rootDir);
-
-        return new SimpleImmutableEntry<>(remFilePath, expected);
+        return new SetupDetails(rootDir, remFilePath, expected);
     }
 
-    private Map.Entry<String, List<Path>> setupFileSuffixMatching() throws IOException {
+    private SetupDetails setupFileSuffixMatching() throws IOException {
         Path targetPath = detectTargetFolder();
         Path rootDir = CommonTestSupportUtils.resolve(targetPath,
                 TEMP_SUBFOLDER_NAME, getClass().getSimpleName(), getCurrentTestName());
@@ -131,7 +169,30 @@ public class SftpDirectoryScannersTest extends AbstractSftpFilesSystemSupport {
 
         Path parentPath = targetPath.getParent();
         String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, rootDir);
+        return new SetupDetails(rootDir, remFilePath, expected);
+    }
+
+    private static class SetupDetails {
+        private final Path rootDir;
+        private final String remFilePath;
+        private final List<Path> expected;
 
-        return new SimpleImmutableEntry<>(remFilePath, expected);
+        SetupDetails(Path rootDir, String remFilePath, List<Path> expected) {
+            this.rootDir = rootDir;
+            this.remFilePath = remFilePath;
+            this.expected = expected;
+        }
+
+        public Path getRootDir() {
+            return rootDir;
+        }
+
+        public String getRemoteFilePath() {
+            return remFilePath;
+        }
+
+        public List<Path> getExpected() {
+            return expected;
+        }
     }
 }
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
index f41ea52..94727ca 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
@@ -304,20 +304,15 @@ public class SftpFileSystemTest extends AbstractSftpFilesSystemSupport {
             return value;
         };
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (FileSystem fs = createSftpFileSystem(session, selector)) {
-                assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
-                Collection<String> views = fs.supportedFileAttributeViews();
-                assertTrue("Universal views (" + SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS + ") not supported: " + views,
-                        views.containsAll(SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS));
-                int expectedVersion = selected.get();
-                assertEquals("Mismatched negotiated version", expectedVersion, ((SftpFileSystem) fs).getVersion());
-                testFileSystem(fs, expectedVersion);
-            }
+        try (ClientSession session = createAuthenticatedClientSession();
+             FileSystem fs = createSftpFileSystem(session, selector)) {
+            assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
+            Collection<String> views = fs.supportedFileAttributeViews();
+            assertTrue("Universal views (" + SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS + ") not supported: " + views,
+                    views.containsAll(SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS));
+            int expectedVersion = selected.get();
+            assertEquals("Mismatched negotiated version", expectedVersion, ((SftpFileSystem) fs).getVersion());
+            testFileSystem(fs, expectedVersion);
         }
     }
 
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/impl/SftpRemotePathChannelTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/impl/SftpRemotePathChannelTest.java
index b104982..a2d52ec 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/impl/SftpRemotePathChannelTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/impl/SftpRemotePathChannelTest.java
@@ -66,29 +66,24 @@ public class SftpRemotePathChannelTest extends AbstractSftpClientTestSupport {
         byte[] expected
                 = (getClass().getName() + "#" + getCurrentTestName() + "(" + new Date() + ")").getBytes(StandardCharsets.UTF_8);
 
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session)) {
-                Path parentPath = targetPath.getParent();
-                String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile);
-
-                try (FileChannel fc = sftp.openRemotePathChannel(
-                        remFilePath, EnumSet.of(
-                                StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE))) {
-                    int writeLen = fc.write(ByteBuffer.wrap(expected));
-                    assertEquals("Mismatched written length", expected.length, writeLen);
-
-                    FileChannel fcPos = fc.position(0L);
-                    assertSame("Mismatched positioned file channel", fc, fcPos);
-
-                    byte[] actual = new byte[expected.length];
-                    int readLen = fc.read(ByteBuffer.wrap(actual));
-                    assertEquals("Mismatched read len", writeLen, readLen);
-                    assertArrayEquals("Mismatched read data", expected, actual);
-                }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session)) {
+            Path parentPath = targetPath.getParent();
+            String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile);
+
+            try (FileChannel fc = sftp.openRemotePathChannel(
+                    remFilePath, EnumSet.of(
+                            StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE))) {
+                int writeLen = fc.write(ByteBuffer.wrap(expected));
+                assertEquals("Mismatched written length", expected.length, writeLen);
+
+                FileChannel fcPos = fc.position(0L);
+                assertSame("Mismatched positioned file channel", fc, fcPos);
+
+                byte[] actual = new byte[expected.length];
+                int readLen = fc.read(ByteBuffer.wrap(actual));
+                assertEquals("Mismatched read len", writeLen, readLen);
+                assertArrayEquals("Mismatched read data", expected, actual);
             }
         }
 
@@ -119,19 +114,14 @@ public class SftpRemotePathChannelTest extends AbstractSftpClientTestSupport {
         Files.deleteIfExists(dstFile);
 
         String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session);
-                 FileChannel srcChannel = sftp.openRemotePathChannel(
-                         remFilePath, EnumSet.of(StandardOpenOption.READ));
-                 FileChannel dstChannel = FileChannel.open(dstFile,
-                         StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
-                long numXfered = srcChannel.transferTo(0L, expected.length, dstChannel);
-                assertEquals("Mismatched reported transfer count", expected.length, numXfered);
-            }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session);
+             FileChannel srcChannel = sftp.openRemotePathChannel(
+                     remFilePath, EnumSet.of(StandardOpenOption.READ));
+             FileChannel dstChannel = FileChannel.open(dstFile,
+                     StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
+            long numXfered = srcChannel.transferTo(0L, expected.length, dstChannel);
+            assertEquals("Mismatched reported transfer count", expected.length, numXfered);
         }
 
         byte[] actual = Files.readAllBytes(dstFile);
@@ -158,21 +148,16 @@ public class SftpRemotePathChannelTest extends AbstractSftpClientTestSupport {
         Files.deleteIfExists(dstFile);
 
         String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session);
-                 FileChannel srcChannel = sftp.openRemotePathChannel(
-                         remFilePath, EnumSet.of(StandardOpenOption.READ));
-                 FileChannel dstChannel = FileChannel.open(dstFile,
-                         StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
-                // SftpRemotePathChannel.DEFAULT_TRANSFER_BUFFER_SIZE > expected.length => Infinite loop
-                long numXfered
-                        = srcChannel.transferTo(0L, SftpModuleProperties.COPY_BUF_SIZE.getRequiredDefault(), dstChannel);
-                assertEquals("Mismatched reported transfer count", expected.length, numXfered);
-            }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session);
+             FileChannel srcChannel = sftp.openRemotePathChannel(
+                     remFilePath, EnumSet.of(StandardOpenOption.READ));
+             FileChannel dstChannel = FileChannel.open(dstFile,
+                     StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
+            // SftpRemotePathChannel.DEFAULT_TRANSFER_BUFFER_SIZE > expected.length => Infinite loop
+            long numXfered
+                    = srcChannel.transferTo(0L, SftpModuleProperties.COPY_BUF_SIZE.getRequiredDefault(), dstChannel);
+            assertEquals("Mismatched reported transfer count", expected.length, numXfered);
         }
 
         byte[] actual = Files.readAllBytes(dstFile);
@@ -203,18 +188,13 @@ public class SftpRemotePathChannelTest extends AbstractSftpClientTestSupport {
         Files.deleteIfExists(dstFile);
 
         String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, dstFile);
-        try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port)
-                .verify(CONNECT_TIMEOUT).getSession()) {
-            session.addPasswordIdentity(getCurrentTestName());
-            session.auth().verify(AUTH_TIMEOUT);
-
-            try (SftpClient sftp = createSftpClient(session);
-                 FileChannel dstChannel = sftp.openRemotePathChannel(
-                         remFilePath, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));
-                 FileChannel srcChannel = FileChannel.open(srcFile, StandardOpenOption.READ)) {
-                long numXfered = dstChannel.transferFrom(srcChannel, 0L, expected.length);
-                assertEquals("Mismatched reported transfer count", expected.length, numXfered);
-            }
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session);
+             FileChannel dstChannel = sftp.openRemotePathChannel(
+                     remFilePath, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE));
+             FileChannel srcChannel = FileChannel.open(srcFile, StandardOpenOption.READ)) {
+            long numXfered = dstChannel.transferFrom(srcChannel, 0L, expected.length);
+            assertEquals("Mismatched reported transfer count", expected.length, numXfered);
         }
 
         byte[] actual = Files.readAllBytes(dstFile);
diff --git a/sshd-spring-sftp/src/test/java/org/apache/sshd/sftp/spring/integration/ApacheSshdSftpSessionFactoryTest.java b/sshd-spring-sftp/src/test/java/org/apache/sshd/sftp/spring/integration/ApacheSshdSftpSessionFactoryTest.java
index e4bf4c5..35127bf 100644
--- a/sshd-spring-sftp/src/test/java/org/apache/sshd/sftp/spring/integration/ApacheSshdSftpSessionFactoryTest.java
+++ b/sshd-spring-sftp/src/test/java/org/apache/sshd/sftp/spring/integration/ApacheSshdSftpSessionFactoryTest.java
@@ -43,6 +43,7 @@ import org.apache.sshd.common.file.FileSystemFactory;
 import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.PathUtils;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.sftp.client.SftpClient;
 import org.apache.sshd.sftp.client.SftpClient.Attributes;
@@ -84,15 +85,6 @@ public class ApacheSshdSftpSessionFactoryTest extends BaseTestSupport {
         }
     };
 
-    private static final Comparator<Path> BY_CASE_INSENSITIVE_FILE_PART = new Comparator<Path>() {
-        @Override
-        public int compare(Path o1, Path o2) {
-            String n1 = (o1 == null) ? null : Objects.toString(o1.getFileName(), null);
-            String n2 = (o2 == null) ? null : Objects.toString(o2.getFileName(), null);
-            return GenericUtils.safeCompare(n1, n2, false);
-        }
-    };
-
     private static final Predicate<String> SYNTHETIC_DIR_ENTRY_NAME = n -> ".".equals(n) || "..".equals(n);
 
     private static SshServer sshd;
@@ -267,7 +259,7 @@ public class ApacheSshdSftpSessionFactoryTest extends BaseTestSupport {
             Path dir = Files.createDirectories(lclSftp.resolve("dir" + index));
             subFolders.add(dir);
         }
-        Collections.sort(subFolders, BY_CASE_INSENSITIVE_FILE_PART);
+        Collections.sort(subFolders, PathUtils.BY_CASE_INSENSITIVE_FILENAME);
 
         List<Path> subFiles = new ArrayList<>();
         for (int index = 1; index <= Byte.SIZE; index++) {
@@ -275,7 +267,7 @@ public class ApacheSshdSftpSessionFactoryTest extends BaseTestSupport {
                     (getClass().getSimpleName() + "#" + getCurrentTestName() + "-" + index).getBytes(StandardCharsets.UTF_8));
             subFiles.add(file);
         }
-        Collections.sort(subFiles, BY_CASE_INSENSITIVE_FILE_PART);
+        Collections.sort(subFiles, PathUtils.BY_CASE_INSENSITIVE_FILENAME);
 
         Path parentPath = targetPath.getParent();
         String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclSftp);