8066943: (fs) Path.relativize() gives incorrect result for ".."
authoralanb
Thu, 25 Aug 2016 10:01:20 +0100
changeset 40549 682c50cd63d0
parent 40548 5a9dbfa8e53e
child 40550 ed2670b26b6e
8066943: (fs) Path.relativize() gives incorrect result for ".." Reviewed-by: prappo, bpb
jdk/src/java.base/unix/classes/sun/nio/fs/UnixPath.java
jdk/src/java.base/windows/classes/sun/nio/fs/WindowsPath.java
jdk/test/java/nio/file/Path/PathOps.java
--- a/jdk/src/java.base/unix/classes/sun/nio/fs/UnixPath.java	Thu Aug 25 09:43:46 2016 +0530
+++ b/jdk/src/java.base/unix/classes/sun/nio/fs/UnixPath.java	Thu Aug 25 10:01:20 2016 +0100
@@ -252,6 +252,21 @@
         return new UnixPath(getFileSystem(), new byte[0]);
     }
 
+
+    // return true if this path has "." or ".."
+    private boolean hasDotOrDotDot() {
+        int n = getNameCount();
+        for (int i=0; i<n; i++) {
+            byte[] bytes = getName(i).path;
+            if ((bytes.length == 1 && bytes[0] == '.'))
+                return true;
+            if ((bytes.length == 2 && bytes[0] == '.') && bytes[1] == '.') {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public UnixFileSystem getFileSystem() {
         return fs;
@@ -405,80 +420,94 @@
 
     @Override
     public UnixPath relativize(Path obj) {
-        UnixPath other = toUnixPath(obj);
-        if (other.equals(this))
+        UnixPath child = toUnixPath(obj);
+        if (child.equals(this))
             return emptyPath();
 
         // can only relativize paths of the same type
-        if (this.isAbsolute() != other.isAbsolute())
+        if (this.isAbsolute() != child.isAbsolute())
             throw new IllegalArgumentException("'other' is different type of Path");
 
         // this path is the empty path
         if (this.isEmpty())
-            return other;
+            return child;
 
-        int bn = this.getNameCount();
-        int cn = other.getNameCount();
+        UnixPath base = this;
+        if (base.hasDotOrDotDot() || child.hasDotOrDotDot()) {
+            base = base.normalize();
+            child = child.normalize();
+        }
+
+        int baseCount = base.getNameCount();
+        int childCount = child.getNameCount();
 
         // skip matching names
-        int n = (bn > cn) ? cn : bn;
+        int n = Math.min(baseCount, childCount);
         int i = 0;
         while (i < n) {
-            if (!this.getName(i).equals(other.getName(i)))
+            if (!base.getName(i).equals(child.getName(i)))
                 break;
             i++;
         }
 
-        int dotdots = bn - i;
-        if (i < cn) {
-            // remaining name components in other
-            UnixPath remainder = other.subpath(i, cn);
-            if (dotdots == 0)
-                return remainder;
+        // remaining elements in child
+        UnixPath childRemaining;
+        boolean isChildEmpty;
+        if (i == childCount) {
+            childRemaining = emptyPath();
+            isChildEmpty = true;
+        } else {
+            childRemaining = child.subpath(i, childCount);
+            isChildEmpty = childRemaining.isEmpty();
+        }
+
+        // matched all of base
+        if (i == baseCount) {
+            return childRemaining;
+        }
 
-            // other is the empty path
-            boolean isOtherEmpty = other.isEmpty();
+        // the remainder of base cannot contain ".."
+        UnixPath baseRemaining = base.subpath(i, baseCount);
+        if (baseRemaining.hasDotOrDotDot()) {
+            throw new IllegalArgumentException("Unable to compute relative "
+                    + " path from " + this + " to " + obj);
+        }
+        if (baseRemaining.isEmpty())
+            return childRemaining;
+
+        // number of ".." needed
+        int dotdots = baseRemaining.getNameCount();
+        if (dotdots == 0) {
+            return childRemaining;
+        }
 
-            // result is a  "../" for each remaining name in base
-            // followed by the remaining names in other. If the remainder is
-            // the empty path then we don't add the final trailing slash.
-            int len = dotdots*3 + remainder.path.length;
-            if (isOtherEmpty) {
-                assert remainder.isEmpty();
-                len--;
+        // result is a  "../" for each remaining name in base followed by the
+        // remaining names in child. If the remainder is the empty path
+        // then we don't add the final trailing slash.
+        int len = dotdots*3 + childRemaining.path.length;
+        if (isChildEmpty) {
+            assert childRemaining.isEmpty();
+            len--;
+        }
+        byte[] result = new byte[len];
+        int pos = 0;
+        while (dotdots > 0) {
+            result[pos++] = (byte)'.';
+            result[pos++] = (byte)'.';
+            if (isChildEmpty) {
+                if (dotdots > 1) result[pos++] = (byte)'/';
+            } else {
+                result[pos++] = (byte)'/';
             }
-            byte[] result = new byte[len];
-            int pos = 0;
-            while (dotdots > 0) {
-                result[pos++] = (byte)'.';
-                result[pos++] = (byte)'.';
-                if (isOtherEmpty) {
-                    if (dotdots > 1) result[pos++] = (byte)'/';
-                } else {
-                    result[pos++] = (byte)'/';
-                }
-                dotdots--;
-            }
-            System.arraycopy(remainder.path, 0, result, pos, remainder.path.length);
-            return new UnixPath(getFileSystem(), result);
-        } else {
-            // no remaining names in other so result is simply a sequence of ".."
-            byte[] result = new byte[dotdots*3 - 1];
-            int pos = 0;
-            while (dotdots > 0) {
-                result[pos++] = (byte)'.';
-                result[pos++] = (byte)'.';
-                // no tailing slash at the end
-                if (dotdots > 1)
-                    result[pos++] = (byte)'/';
-                dotdots--;
-            }
-            return new UnixPath(getFileSystem(), result);
+            dotdots--;
         }
+        System.arraycopy(childRemaining.path,0, result, pos,
+                             childRemaining.path.length);
+        return new UnixPath(getFileSystem(), result);
     }
 
     @Override
-    public Path normalize() {
+    public UnixPath normalize() {
         final int count = getNameCount();
         if (count == 0 || isEmpty())
             return this;
--- a/jdk/src/java.base/windows/classes/sun/nio/fs/WindowsPath.java	Thu Aug 25 09:43:46 2016 +0530
+++ b/jdk/src/java.base/windows/classes/sun/nio/fs/WindowsPath.java	Thu Aug 25 10:01:20 2016 +0100
@@ -375,57 +375,108 @@
         return (WindowsPath)path;
     }
 
+    // return true if this path has "." or ".."
+    private boolean hasDotOrDotDot() {
+        int n = getNameCount();
+        for (int i=0; i<n; i++) {
+            String name = elementAsString(i);
+            if (name.length() == 1 && name.charAt(0) == '.')
+                return true;
+            if (name.length() == 2
+                    && name.charAt(0) == '.' && name.charAt(1) == '.')
+                return true;
+        }
+        return false;
+    }
+
     @Override
     public WindowsPath relativize(Path obj) {
-        WindowsPath other = toWindowsPath(obj);
-        if (this.equals(other))
+        WindowsPath child = toWindowsPath(obj);
+        if (this.equals(child))
             return emptyPath();
 
         // can only relativize paths of the same type
-        if (this.type != other.type)
+        if (this.type != child.type)
             throw new IllegalArgumentException("'other' is different type of Path");
 
         // can only relativize paths if root component matches
-        if (!this.root.equalsIgnoreCase(other.root))
+        if (!this.root.equalsIgnoreCase(child.root))
             throw new IllegalArgumentException("'other' has different root");
 
         // this path is the empty path
         if (this.isEmpty())
-            return other;
+            return child;
+
 
-        int bn = this.getNameCount();
-        int cn = other.getNameCount();
+        WindowsPath base = this;
+        if (base.hasDotOrDotDot() || child.hasDotOrDotDot()) {
+            base = base.normalize();
+            child = child.normalize();
+        }
+
+        int baseCount = base.getNameCount();
+        int childCount = child.getNameCount();
 
         // skip matching names
-        int n = (bn > cn) ? cn : bn;
+        int n = Math.min(baseCount, childCount);
         int i = 0;
         while (i < n) {
-            if (!this.getName(i).equals(other.getName(i)))
+            if (!base.getName(i).equals(child.getName(i)))
                 break;
             i++;
         }
 
-        // append ..\ for remaining names in the base
+        // remaining elements in child
+        WindowsPath childRemaining;
+        boolean isChildEmpty;
+        if (i == childCount) {
+            childRemaining = emptyPath();
+            isChildEmpty = true;
+        } else {
+            childRemaining = child.subpath(i, childCount);
+            isChildEmpty = childRemaining.isEmpty();
+        }
+
+        // matched all of base
+        if (i == baseCount) {
+            return childRemaining;
+        }
+
+        // the remainder of base cannot contain ".."
+        WindowsPath baseRemaining = base.subpath(i, baseCount);
+        if (baseRemaining.hasDotOrDotDot()) {
+            throw new IllegalArgumentException("Unable to compute relative "
+                    + " path from " + this + " to " + obj);
+        }
+        if (baseRemaining.isEmpty())
+            return childRemaining;
+
+        // number of ".." needed
+        int dotdots = baseRemaining.getNameCount();
+        if (dotdots == 0) {
+            return childRemaining;
+        }
+
         StringBuilder result = new StringBuilder();
-        for (int j=i; j<bn; j++) {
+        for (int j=0; j<dotdots; j++) {
             result.append("..\\");
         }
 
         // append remaining names in child
-        if (!other.isEmpty()) {
-            for (int j=i; j<cn; j++) {
-                result.append(other.getName(j).toString());
+        if (!isChildEmpty) {
+            for (int j=0; j<childRemaining.getNameCount(); j++) {
+                result.append(childRemaining.getName(j).toString());
                 result.append("\\");
             }
         }
 
-        // drop trailing slash in result
+        // drop trailing slash
         result.setLength(result.length()-1);
         return createFromNormalizedPath(getFileSystem(), result.toString());
     }
 
     @Override
-    public Path normalize() {
+    public WindowsPath normalize() {
         final int count = getNameCount();
         if (count == 0 || isEmpty())
             return this;
--- a/jdk/test/java/nio/file/Path/PathOps.java	Thu Aug 25 09:43:46 2016 +0530
+++ b/jdk/test/java/nio/file/Path/PathOps.java	Thu Aug 25 10:01:20 2016 +0100
@@ -197,6 +197,19 @@
         return this;
     }
 
+    PathOps relativizeFail(String other) {
+        out.format("test relativize %s\n", other);
+        checkPath();
+        Path that = FileSystems.getDefault().getPath(other);
+        try {
+            Path result = path.relativize(that);
+            out.format("\tExpected: IllegalArgumentException");
+            out.format("\tActual: %s\n",  result);
+            fail();
+        } catch (IllegalArgumentException expected) { }
+        return this;
+    }
+
     PathOps normalize(String expected) {
         out.println("check normalized path");
         checkPath();
@@ -572,29 +585,709 @@
             .resolveSibling("C:\\", "C:\\");
 
         // relativize
-        test("foo")
-            .relativize("foo", "")
-            .relativize("bar", "..\\bar")
+        test("C:\\a")
+            .relativize("C:\\a", "")
+            .relativize("C:\\", "..")
+            .relativize("C:\\.", "..")
+            .relativize("C:\\..", "..")
+            .relativize("C:\\..\\..", "..")
+            .relativize("C:\\a\\b", "b")
+            .relativize("C:\\a\\b\\c", "b\\c")
+            .relativize("C:\\a\\.", "")        // "." also valid
+            .relativize("C:\\a\\..", "..")
+            .relativize("C:\\x", "..\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("C:\\a\\b")
+            .relativize("C:\\a\\b", "")
+            .relativize("C:\\a", "..")
+            .relativize("C:\\", "..\\..")
+            .relativize("C:\\.", "..\\..")
+            .relativize("C:\\..", "..\\..")
+            .relativize("C:\\..\\..", "..\\..")
+            .relativize("C:\\a\\b\\c", "c")
+            .relativize("C:\\a\\.", "..")
+            .relativize("C:\\a\\..", "..\\..")
+            .relativize("C:\\x", "..\\..\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("C:\\a\\b\\c")
+            .relativize("C:\\a\\b\\c", "")
+            .relativize("C:\\a\\b", "..")
+            .relativize("C:\\a", "..\\..")
+            .relativize("C:\\", "..\\..\\..")
+            .relativize("C:\\.", "..\\..\\..")
+            .relativize("C:\\..", "..\\..\\..")
+            .relativize("C:\\..\\..", "..\\..\\..")
+            .relativize("C:\\..\\..\\..", "..\\..\\..")
+            .relativize("C:\\..\\..\\..\\..", "..\\..\\..")
+            .relativize("C:\\a\\b\\c\\d", "d")
+            .relativize("C:\\a\\b\\c\\d\\e", "d\\e")
+            .relativize("C:\\a\\b\\c\\.", "")        // "." also valid
+            .relativize("C:\\a\\b\\c\\..", "..")
+            .relativize("C:\\a\\x", "..\\..\\x")
+            .relativize("C:\\x", "..\\..\\..\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("C:\\..\\a")
+            .relativize("C:\\a", "")
+            .relativize("C:\\", "..")
+            .relativize("C:\\.", "..")
+            .relativize("C:\\..", "..")
+            .relativize("C:\\..\\..", "..")
+            .relativize("C:\\a\\b", "b")
+            .relativize("C:\\a\\b\\c", "b\\c")
+            .relativize("C:\\a\\.", "")        // "." also valid
+            .relativize("C:\\a\\..", "..")
+            .relativize("C:\\x", "..\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("C:\\..\\a\\b")
+            .relativize("C:\\a\\b", "")
+            .relativize("C:\\a", "..")
+            .relativize("C:\\", "..\\..")
+            .relativize("C:\\.", "..\\..")
+            .relativize("C:\\..", "..\\..")
+            .relativize("C:\\..\\..", "..\\..")
+            .relativize("C:\\..\\..\\..", "..\\..")
+            .relativize("C:\\..\\..\\..\\..", "..\\..")
+            .relativize("C:\\a\\b\\c", "c")
+            .relativize("C:\\a\\b\\.", "")        // "." also valid
+            .relativize("C:\\a\\b\\..", "..")
+            .relativize("C:\\a\\x", "..\\x")
+            .relativize("C:\\x", "..\\..\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("C:\\..\\..\\a\\b")
+            .relativize("C:\\a\\b", "")
+            .relativize("C:\\a", "..")
+            .relativize("C:\\", "..\\..")
+            .relativize("C:\\.", "..\\..")
+            .relativize("C:\\..", "..\\..")
+            .relativize("C:\\..\\..", "..\\..")
+            .relativize("C:\\..\\..\\..", "..\\..")
+            .relativize("C:\\..\\..\\..\\..", "..\\..")
+            .relativize("C:\\a\\b\\c", "c")
+            .relativize("C:\\a\\b\\.", "")        // "." also valid
+            .relativize("C:\\a\\b\\..", "..")
+            .relativize("C:\\a\\x", "..\\x")
+            .relativize("C:\\x", "..\\..\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("C:\\..\\a\\b\\c")
+            .relativize("C:\\a\\b\\c", "")
+            .relativize("C:\\a\\b", "..")
+            .relativize("C:\\a", "..\\..")
+            .relativize("C:\\", "..\\..\\..")
+            .relativize("C:\\.", "..\\..\\..")
+            .relativize("C:\\..", "..\\..\\..")
+            .relativize("C:\\..\\..", "..\\..\\..")
+            .relativize("C:\\..\\..\\..", "..\\..\\..")
+            .relativize("C:\\..\\..\\..\\..", "..\\..\\..")
+            .relativize("C:\\a\\b\\c\\d", "d")
+            .relativize("C:\\a\\b\\c\\d\\e", "d\\e")
+            .relativize("C:\\a\\b\\c\\.", "")        // "." also valid
+            .relativize("C:\\a\\b\\c\\..", "..")
+            .relativize("C:\\a\\x", "..\\..\\x")
+            .relativize("C:\\x", "..\\..\\..\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("C:\\..\\..\\a\\b\\c")
+            .relativize("C:\\a\\b\\c", "")
+            .relativize("C:\\a\\b", "..")
+            .relativize("C:\\a", "..\\..")
+            .relativize("C:\\", "..\\..\\..")
+            .relativize("C:\\.", "..\\..\\..")
+            .relativize("C:\\..", "..\\..\\..")
+            .relativize("C:\\..\\..", "..\\..\\..")
+            .relativize("C:\\..\\..\\..", "..\\..\\..")
+            .relativize("C:\\..\\..\\..\\..", "..\\..\\..")
+            .relativize("C:\\a\\b\\c\\d", "d")
+            .relativize("C:\\a\\b\\c\\d\\e", "d\\e")
+            .relativize("C:\\a\\b\\c\\.", "")        // "." also valid
+            .relativize("C:\\a\\b\\c\\..", "..")
+            .relativize("C:\\a\\x", "..\\..\\x")
+            .relativize("C:\\x", "..\\..\\..\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("C:\\..\\..\\..\\a\\b\\c")
+            .relativize("C:\\a\\b\\c", "")
+            .relativize("C:\\a\\b", "..")
+            .relativize("C:\\a", "..\\..")
+            .relativize("C:\\", "..\\..\\..")
+            .relativize("C:\\.", "..\\..\\..")
+            .relativize("C:\\..", "..\\..\\..")
+            .relativize("C:\\..\\..", "..\\..\\..")
+            .relativize("C:\\..\\..\\..", "..\\..\\..")
+            .relativize("C:\\..\\..\\..\\..", "..\\..\\..")
+            .relativize("C:\\a\\b\\c\\d", "d")
+            .relativize("C:\\a\\b\\c\\d\\e", "d\\e")
+            .relativize("C:\\a\\b\\c\\.", "")        // "." also valid
+            .relativize("C:\\a\\b\\c\\..", "..")
+            .relativize("C:\\a\\x", "..\\..\\x")
+            .relativize("C:\\x", "..\\..\\..\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("C:\\.\\a")
+            .relativize("C:\\a", "")
+            .relativize("C:\\", "..")
+            .relativize("C:\\.", "..")
+            .relativize("C:\\..", "..")
+            .relativize("C:\\..\\..", "..")
+            .relativize("C:\\a\\b", "b")
+            .relativize("C:\\a\\b\\c", "b\\c")
+            .relativize("C:\\a\\.", "")        // "." also valid
+            .relativize("C:\\a\\..", "..")
+            .relativize("C:\\x", "..\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("C:\\..\\a")
+            .relativize("C:\\a", "")
+            .relativize("C:\\", "..")
+            .relativize("C:\\.", "..")
+            .relativize("C:\\..", "..")
+            .relativize("C:\\..\\..", "..")
+            .relativize("C:\\a\\b", "b")
+            .relativize("C:\\a\\b\\c", "b\\c")
+            .relativize("C:\\a\\.", "")        // "." also valid
+            .relativize("C:\\a\\..", "..")
+            .relativize("C:\\x", "..\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("C:\\a\\..")
+            .relativize("C:\\a", "a")
+            .relativize("C:\\", "")          // "." is also valid
+            .relativize("C:\\.", "")
+            .relativize("C:\\..", "")
+            .relativize("C:\\..\\..", "")
+            .relativize("C:\\a\\.", "a")
+            .relativize("C:\\a\\..", "")
+            .relativize("C:\\x", "x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("C:a")
+            .relativize("C:a", "")
+            .relativize("C:", "..")
+            .relativize("C:.", "..")
+            .relativize("C:..", "..\\..")
+            .relativize("C:..\\..", "..\\..\\..")
+            .relativize("C:.\\..", "..\\..")
+            .relativize("C:a\\b", "b")
+            .relativize("C:a\\b\\c", "b\\c")
+            .relativize("C:..\\x", "..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x");
+        test("C:a\\b")
+            .relativize("C:a\\b", "")
+            .relativize("C:a", "..")
+            .relativize("C:", "..\\..")
+            .relativize("C:.", "..\\..")
+            .relativize("C:..", "..\\..\\..")
+            .relativize("C:..\\..", "..\\..\\..\\..")
+            .relativize("C:.\\..", "..\\..\\..")
+            .relativize("C:a\\b\\c", "c")
+            .relativize("C:..\\x", "..\\..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x");
+        test("C:a\\b\\c")
+            .relativize("C:a\\b\\c", "")
+            .relativize("C:a\\b", "..")
+            .relativize("C:a", "..\\..")
+            .relativize("C:", "..\\..\\..")
+            .relativize("C:.", "..\\..\\..")
+            .relativize("C:..", "..\\..\\..\\..")
+            .relativize("C:..\\..", "..\\..\\..\\..\\..")
+            .relativize("C:.\\..", "..\\..\\..\\..")
+            .relativize("C:a\\b\\c\\d", "d")
+            .relativize("C:a\\b\\c\\d\\e", "d\\e")
+            .relativize("C:a\\x", "..\\..\\x")
+            .relativize("C:..\\x", "..\\..\\..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x");
+        test("C:")
+            .relativize("C:a", "a")
+            .relativize("C:a\\b\\c", "a\\b\\c")
+            .relativize("C:", "")
+            .relativize("C:.", "")              // "" also valid
+            .relativize("C:..", "..")
+            .relativize("C:..\\..", "..\\..")
+            .relativize("C:.\\..", "..")
+            .relativizeFail("C:\\x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x");
+        test("C:..")
+            .relativize("C:..\\a", "a")
+            .relativize("C:..", "")
+            .relativize("C:.\\..", "")
+            .relativizeFail("C:\\x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("x");
+        test("C:..\\a")
+            .relativize("C:..\\a\\b", "b")
+            .relativize("C:..\\a", "")
+            .relativize("C:..", "..")
+            .relativize("C:.\\..", "..")
+            .relativizeFail("C:\\x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("x");
+        test("C:..\\a\\b")
+            .relativize("C:..\\a\\b\\c", "c")
+            .relativize("C:..\\a\\b", "")
+            .relativize("C:..\\a", "..")
+            .relativize("C:..", "..\\..")
+            .relativize("C:.\\..", "..\\..")
+            .relativizeFail("C:\\x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x")
+            .relativizeFail("")
+            .relativizeFail("x");
+        test("C:a\\..")
+            .relativize("C:b", "b")
+            .relativize("C:", "")
+            .relativize("C:.", "")       // "." also valid
+            .relativize("C:..", "..")
+            .relativize("C:a\\..\\b", "b")
+            .relativize("C:a\\..", "")
+            .relativize("C:..\\b", "..\\b")
+            .relativize("C:b\\..", "")
+            .relativizeFail("C:\\x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x")
+            .relativizeFail("x");
+        test("C:a\\..\\b")
+            .relativize("C:a\\..\\b", "")
+            .relativize("C:a\\..", "..")
+            .relativize("C:", "..")
+            .relativize("C:.", "..")
+            .relativize("C:..", "..\\..")
+            .relativize("C:b", "")
+            .relativize("C:c", "..\\c")
+            .relativize("C:..\\c", "..\\..\\c")
+            .relativize("C:a\\..\\b\\c", "c")
+            .relativizeFail("C:\\x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x")
+            .relativizeFail("x");
+        test("\\a")
+            .relativize("\\a", "")
+            .relativize("\\", "..")
+            .relativize("\\.", "..")
+            .relativize("\\..", "..")
+            .relativize("\\..\\..", "..")
+            .relativize("\\a\\b", "b")
+            .relativize("\\a\\b\\c", "b\\c")
+            .relativize("\\a\\.", "")        // "." also valid
+            .relativize("\\a\\..", "..")
+            .relativize("\\x", "..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("\\a\\b")
+            .relativize("\\a\\b", "")
+            .relativize("\\a", "..")
+            .relativize("\\", "..\\..")
+            .relativize("\\.", "..\\..")
+            .relativize("\\..", "..\\..")
+            .relativize("\\..\\..", "..\\..")
+            .relativize("\\a\\b\\c", "c")
+            .relativize("\\a\\.", "..")
+            .relativize("\\a\\..", "..\\..")
+            .relativize("\\x", "..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("\\a\\b\\c")
+            .relativize("\\a\\b\\c", "")
+            .relativize("\\a\\b", "..")
+            .relativize("\\a", "..\\..")
+            .relativize("\\", "..\\..\\..")
+            .relativize("\\.", "..\\..\\..")
+            .relativize("\\..", "..\\..\\..")
+            .relativize("\\..\\..", "..\\..\\..")
+            .relativize("\\..\\..\\..", "..\\..\\..")
+            .relativize("\\..\\..\\..\\..", "..\\..\\..")
+            .relativize("\\a\\b\\c\\d", "d")
+            .relativize("\\a\\b\\c\\d\\e", "d\\e")
+            .relativize("\\a\\b\\c\\.", "")        // "." also valid
+            .relativize("\\a\\b\\c\\..", "..")
+            .relativize("\\a\\x", "..\\..\\x")
+            .relativize("\\x", "..\\..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("\\..\\a")
+            .relativize("\\a", "")
+            .relativize("\\", "..")
+            .relativize("\\.", "..")
+            .relativize("\\..", "..")
+            .relativize("\\..\\..", "..")
+            .relativize("\\a\\b", "b")
+            .relativize("\\a\\b\\c", "b\\c")
+            .relativize("\\a\\.", "")        // "." also valid
+            .relativize("\\a\\..", "..")
+            .relativize("\\x", "..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("\\..\\a\\b")
+            .relativize("\\a\\b", "")
+            .relativize("\\a", "..")
+            .relativize("\\", "..\\..")
+            .relativize("\\.", "..\\..")
+            .relativize("\\..", "..\\..")
+            .relativize("\\..\\..", "..\\..")
+            .relativize("\\..\\..\\..", "..\\..")
+            .relativize("\\..\\..\\..\\..", "..\\..")
+            .relativize("\\a\\b\\c", "c")
+            .relativize("\\a\\b\\.", "")        // "." also valid
+            .relativize("\\a\\b\\..", "..")
+            .relativize("\\a\\x", "..\\x")
+            .relativize("\\x", "..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("\\..\\..\\a\\b")
+            .relativize("\\a\\b", "")
+            .relativize("\\a", "..")
+            .relativize("\\", "..\\..")
+            .relativize("\\.", "..\\..")
+            .relativize("\\..", "..\\..")
+            .relativize("\\..\\..", "..\\..")
+            .relativize("\\..\\..\\..", "..\\..")
+            .relativize("\\..\\..\\..\\..", "..\\..")
+            .relativize("\\a\\b\\c", "c")
+            .relativize("\\a\\b\\.", "")        // "." also valid
+            .relativize("\\a\\b\\..", "..")
+            .relativize("\\a\\x", "..\\x")
+            .relativize("\\x", "..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("\\..\\a\\b\\c")
+            .relativize("\\a\\b\\c", "")
+            .relativize("\\a\\b", "..")
+            .relativize("\\a", "..\\..")
+            .relativize("\\", "..\\..\\..")
+            .relativize("\\.", "..\\..\\..")
+            .relativize("\\..", "..\\..\\..")
+            .relativize("\\..\\..", "..\\..\\..")
+            .relativize("\\..\\..\\..", "..\\..\\..")
+            .relativize("\\..\\..\\..\\..", "..\\..\\..")
+            .relativize("\\a\\b\\c\\d", "d")
+            .relativize("\\a\\b\\c\\d\\e", "d\\e")
+            .relativize("\\a\\b\\c\\.", "")        // "." also valid
+            .relativize("\\a\\b\\c\\..", "..")
+            .relativize("\\a\\x", "..\\..\\x")
+            .relativize("\\x", "..\\..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("\\..\\..\\a\\b\\c")
+            .relativize("\\a\\b\\c", "")
+            .relativize("\\a\\b", "..")
+            .relativize("\\a", "..\\..")
+            .relativize("\\", "..\\..\\..")
+            .relativize("\\.", "..\\..\\..")
+            .relativize("\\..", "..\\..\\..")
+            .relativize("\\..\\..", "..\\..\\..")
+            .relativize("\\..\\..\\..", "..\\..\\..")
+            .relativize("\\..\\..\\..\\..", "..\\..\\..")
+            .relativize("\\a\\b\\c\\d", "d")
+            .relativize("\\a\\b\\c\\d\\e", "d\\e")
+            .relativize("\\a\\b\\c\\.", "")        // "." also valid
+            .relativize("\\a\\b\\c\\..", "..")
+            .relativize("\\a\\x", "..\\..\\x")
+            .relativize("\\x", "..\\..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("\\..\\..\\..\\a\\b\\c")
+            .relativize("\\a\\b\\c", "")
+            .relativize("\\a\\b", "..")
+            .relativize("\\a", "..\\..")
+            .relativize("\\", "..\\..\\..")
+            .relativize("\\.", "..\\..\\..")
+            .relativize("\\..", "..\\..\\..")
+            .relativize("\\..\\..", "..\\..\\..")
+            .relativize("\\..\\..\\..", "..\\..\\..")
+            .relativize("\\..\\..\\..\\..", "..\\..\\..")
+            .relativize("\\a\\b\\c\\d", "d")
+            .relativize("\\a\\b\\c\\d\\e", "d\\e")
+            .relativize("\\a\\b\\c\\.", "")        // "." also valid
+            .relativize("\\a\\b\\c\\..", "..")
+            .relativize("\\a\\x", "..\\..\\x")
+            .relativize("\\x", "..\\..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("\\.\\a")
+            .relativize("\\a", "")
+            .relativize("\\", "..")
+            .relativize("\\.", "..")
+            .relativize("\\..", "..")
+            .relativize("\\..\\..", "..")
+            .relativize("\\a\\b", "b")
+            .relativize("\\a\\b\\c", "b\\c")
+            .relativize("\\a\\.", "")        // "." also valid
+            .relativize("\\a\\..", "..")
+            .relativize("\\x", "..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("\\..\\a")
+            .relativize("\\a", "")
+            .relativize("\\", "..")
+            .relativize("\\.", "..")
+            .relativize("\\..", "..")
+            .relativize("\\..\\..", "..")
+            .relativize("\\a\\b", "b")
+            .relativize("\\a\\b\\c", "b\\c")
+            .relativize("\\a\\.", "")        // "." also valid
+            .relativize("\\a\\..", "..")
+            .relativize("\\x", "..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("\\a\\..")
+            .relativize("\\a", "a")
+            .relativize("\\", "")          // "." is also valid
+            .relativize("\\.", "")
+            .relativize("\\..", "")
+            .relativize("\\..\\..", "")
+            .relativize("\\a\\.", "a")
+            .relativize("\\a\\..", "")
+            .relativize("\\x", "x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("\\")
+            .relativize("\\a", "a")
+            .relativize("\\", "")          // "." is also valid
+            .relativize("\\.", "")
+            .relativize("\\..", "")
+            .relativize("\\..\\..", "")
+            .relativize("\\a\\.", "a")
+            .relativize("\\a\\..", "")
+            .relativize("\\x", "x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("a")
+            .relativize("a", "")
+            .relativize("", "..")
+            .relativize(".", "..")
             .relativize("..", "..\\..")
-            .relativize("", "..");
-        test("foo\\bar")
-            .relativize("foo\\bar", "")
-            .relativize("foo", "..")
-            .relativize("gus", "..\\..\\gus")
+            .relativize("..\\..", "..\\..\\..")
+            .relativize(".\\..", "..\\..")
+            .relativize("a\\b", "b")
+            .relativize("a\\b\\c", "b\\c")
+            .relativize("..\\x", "..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x");
+        test("a\\b")
+            .relativize("a\\b", "")
+            .relativize("a", "..")
+            .relativize("", "..\\..")
+            .relativize(".", "..\\..")
             .relativize("..", "..\\..\\..")
-            .relativize("", "..\\..");
-        test("C:\\a\\b\\c")
-            .relativize("C:\\a", "..\\..")
-            .relativize("C:\\a\\b\\c", "")
-            .relativize("C:\\x", "..\\..\\..\\x");
-        test("\\\\server\\share\\foo")
-            .relativize("\\\\server\\share\\bar", "..\\bar")
-            .relativize("\\\\server\\share\\foo", "");
+            .relativize("..\\..", "..\\..\\..\\..")
+            .relativize(".\\..", "..\\..\\..")
+            .relativize("a\\b\\c", "c")
+            .relativize("..\\x", "..\\..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x");
+        test("a\\b\\c")
+            .relativize("a\\b\\c", "")
+            .relativize("a\\b", "..")
+            .relativize("a", "..\\..")
+            .relativize("", "..\\..\\..")
+            .relativize(".", "..\\..\\..")
+            .relativize("..", "..\\..\\..\\..")
+            .relativize("..\\..", "..\\..\\..\\..\\..")
+            .relativize(".\\..", "..\\..\\..\\..")
+            .relativize("a\\b\\c\\d", "d")
+            .relativize("a\\b\\c\\d\\e", "d\\e")
+            .relativize("a\\x", "..\\..\\x")
+            .relativize("..\\x", "..\\..\\..\\..\\x")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x");
         test("")
             .relativize("a", "a")
             .relativize("a\\b\\c", "a\\b\\c")
+            .relativize("", "")
+            .relativize(".", ".")
             .relativize("..", "..")
-            .relativize("", "");
+            .relativize("..\\..", "..\\..")
+            .relativize(".\\..", ".\\..")     // ".." also valid
+            .relativizeFail("\\")
+            .relativizeFail("\\x");
+        test("..")
+            .relativize("..\\a", "a")
+            .relativize("..", "")
+            .relativize(".\\..", "")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("x");
+        test("..\\a")
+            .relativize("..\\a\\b", "b")
+            .relativize("..\\a", "")
+            .relativize("..", "..")
+            .relativize(".\\..", "..")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("x");
+        test("..\\a\\b")
+            .relativize("..\\a\\b\\c", "c")
+            .relativize("..\\a\\b", "")
+            .relativize("..\\a", "..")
+            .relativize("..", "..\\..")
+            .relativize(".\\..", "..\\..")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x")
+            .relativizeFail("")
+            .relativizeFail("x");
+        test("a\\..")
+            .relativize("b", "b")
+            .relativize("", "")
+            .relativize(".", "")       // "." also valid
+            .relativize("..", "..")
+            .relativize("a\\..\\b", "b")
+            .relativize("a\\..", "")
+            .relativize("..\\b", "..\\b")
+            .relativize("b\\..", "")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x");
+        test("a\\..\\b")
+            .relativize("a\\..\\b", "")
+            .relativize("a\\..", "..")
+            .relativize("", "..")
+            .relativize(".", "..")
+            .relativize("..", "..\\..")
+            .relativize("b", "")
+            .relativize("c", "..\\c")
+            .relativize("..\\c", "..\\..\\c")
+            .relativize("a\\..\\b\\c", "c")
+            .relativizeFail("C:\\x")
+            .relativizeFail("C:x")
+            .relativizeFail("\\")
+            .relativizeFail("\\x");
 
         // normalize
         test("C:\\")
@@ -971,20 +1664,324 @@
             .resolve("", "");
 
         // relativize
+        test("/a")
+            .relativize("/a", "")
+            .relativize("/", "..")
+            .relativize("/.", "..")
+            .relativize("/..", "..")
+            .relativize("/../..", "..")
+            .relativize("/a/b", "b")
+            .relativize("/a/b/c", "b/c")
+            .relativize("/a/.", "")        // "." also valid
+            .relativize("/a/..", "..")
+            .relativize("/x", "../x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("/a/b")
+            .relativize("/a/b", "")
+            .relativize("/a", "..")
+            .relativize("/", "../..")
+            .relativize("/.", "../..")
+            .relativize("/..", "../..")
+            .relativize("/../..", "../..")
+            .relativize("/a/b/c", "c")
+            .relativize("/a/.", "..")
+            .relativize("/a/..", "../..")
+            .relativize("/x", "../../x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
         test("/a/b/c")
             .relativize("/a/b/c", "")
+            .relativize("/a/b", "..")
+            .relativize("/a", "../..")
+            .relativize("/", "../../..")
+            .relativize("/.", "../../..")
+            .relativize("/..", "../../..")
+            .relativize("/../..", "../../..")
+            .relativize("/../../..", "../../..")
+            .relativize("/../../../..", "../../..")
+            .relativize("/a/b/c/d", "d")
             .relativize("/a/b/c/d/e", "d/e")
+            .relativize("/a/b/c/.", "")        // "." also valid
+            .relativize("/a/b/c/..", "..")
+            .relativize("/a/x", "../../x")
+            .relativize("/x", "../../../x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("/../a")
+            .relativize("/a", "")
+            .relativize("/", "..")
+            .relativize("/.", "..")
+            .relativize("/..", "..")
+            .relativize("/../..", "..")
+            .relativize("/a/b", "b")
+            .relativize("/a/b/c", "b/c")
+            .relativize("/a/.", "")        // "." also valid
+            .relativize("/a/..", "..")
+            .relativize("/x", "../x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("/../a/b")
+            .relativize("/a/b", "")
+            .relativize("/a", "..")
+            .relativize("/", "../..")
+            .relativize("/.", "../..")
+            .relativize("/..", "../..")
+            .relativize("/../..", "../..")
+            .relativize("/../../..", "../..")
+            .relativize("/../../../..", "../..")
+            .relativize("/a/b/c", "c")
+            .relativize("/a/b/.", "")        // "." also valid
+            .relativize("/a/b/..", "..")
+            .relativize("/a/x", "../x")
+            .relativize("/x", "../../x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("/../../a/b")
+            .relativize("/a/b", "")
+            .relativize("/a", "..")
+            .relativize("/", "../..")
+            .relativize("/.", "../..")
+            .relativize("/..", "../..")
+            .relativize("/../..", "../..")
+            .relativize("/../../..", "../..")
+            .relativize("/../../../..", "../..")
+            .relativize("/a/b/c", "c")
+            .relativize("/a/b/.", "")        // "." also valid
+            .relativize("/a/b/..", "..")
+            .relativize("/a/x", "../x")
+            .relativize("/x", "../../x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("/../a/b/c")
+            .relativize("/a/b/c", "")
+            .relativize("/a/b", "..")
+            .relativize("/a", "../..")
+            .relativize("/", "../../..")
+            .relativize("/.", "../../..")
+            .relativize("/..", "../../..")
+            .relativize("/../..", "../../..")
+            .relativize("/../../..", "../../..")
+            .relativize("/../../../..", "../../..")
+            .relativize("/a/b/c/d", "d")
+            .relativize("/a/b/c/d/e", "d/e")
+            .relativize("/a/b/c/.", "")        // "." also valid
+            .relativize("/a/b/c/..", "..")
+            .relativize("/a/x", "../../x")
+            .relativize("/x", "../../../x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("/../../a/b/c")
+            .relativize("/a/b/c", "")
+            .relativize("/a/b", "..")
+            .relativize("/a", "../..")
+            .relativize("/", "../../..")
+            .relativize("/.", "../../..")
+            .relativize("/..", "../../..")
+            .relativize("/../..", "../../..")
+            .relativize("/../../..", "../../..")
+            .relativize("/../../../..", "../../..")
+            .relativize("/a/b/c/d", "d")
+            .relativize("/a/b/c/d/e", "d/e")
+            .relativize("/a/b/c/.", "")        // "." also valid
+            .relativize("/a/b/c/..", "..")
             .relativize("/a/x", "../../x")
-            .relativize("/x", "../../../x");
+            .relativize("/x", "../../../x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("/../../../a/b/c")
+            .relativize("/a/b/c", "")
+            .relativize("/a/b", "..")
+            .relativize("/a", "../..")
+            .relativize("/", "../../..")
+            .relativize("/.", "../../..")
+            .relativize("/..", "../../..")
+            .relativize("/../..", "../../..")
+            .relativize("/../../..", "../../..")
+            .relativize("/../../../..", "../../..")
+            .relativize("/a/b/c/d", "d")
+            .relativize("/a/b/c/d/e", "d/e")
+            .relativize("/a/b/c/.", "")        // "." also valid
+            .relativize("/a/b/c/..", "..")
+            .relativize("/a/x", "../../x")
+            .relativize("/x", "../../../x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("/./a")
+            .relativize("/a", "")
+            .relativize("/", "..")
+            .relativize("/.", "..")
+            .relativize("/..", "..")
+            .relativize("/../..", "..")
+            .relativize("/a/b", "b")
+            .relativize("/a/b/c", "b/c")
+            .relativize("/a/.", "")        // "." also valid
+            .relativize("/a/..", "..")
+            .relativize("/x", "../x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("/../a")
+            .relativize("/a", "")
+            .relativize("/", "..")
+            .relativize("/.", "..")
+            .relativize("/..", "..")
+            .relativize("/../..", "..")
+            .relativize("/a/b", "b")
+            .relativize("/a/b/c", "b/c")
+            .relativize("/a/.", "")        // "." also valid
+            .relativize("/a/..", "..")
+            .relativize("/x", "../x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("/a/..")
+            .relativize("/a", "a")
+            .relativize("/", "")          // "." is also valid
+            .relativize("/.", "")
+            .relativize("/..", "")
+            .relativize("/../..", "")
+            .relativize("/a/.", "a")
+            .relativize("/a/..", "")
+            .relativize("/x", "x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("/")
+            .relativize("/a", "a")
+            .relativize("/", "")          // "." is also valid
+            .relativize("/.", "")
+            .relativize("/..", "")
+            .relativize("/../..", "")
+            .relativize("/a/.", "a")
+            .relativize("/a/..", "")
+            .relativize("/x", "x")
+            .relativizeFail("x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("..");
+        test("a")
+            .relativize("a", "")
+            .relativize("", "..")
+            .relativize(".", "..")
+            .relativize("..", "../..")
+            .relativize("../..", "../../..")
+            .relativize("./..", "../..")
+            .relativize("a/b", "b")
+            .relativize("a/b/c", "b/c")
+            .relativize("../x", "../../x")
+            .relativizeFail("/")
+            .relativizeFail("/x");
+        test("a/b")
+            .relativize("a/b", "")
+            .relativize("a", "..")
+            .relativize("", "../..")
+            .relativize(".", "../..")
+            .relativize("..", "../../..")
+            .relativize("../..", "../../../..")
+            .relativize("./..", "../../..")
+            .relativize("a/b/c", "c")
+            .relativize("../x", "../../../x")
+            .relativizeFail("/")
+            .relativizeFail("/x");
         test("a/b/c")
+            .relativize("a/b/c", "")
+            .relativize("a/b", "..")
+            .relativize("a", "../..")
+            .relativize("", "../../..")
+            .relativize(".", "../../..")
+            .relativize("..", "../../../..")
+            .relativize("../..", "../../../../..")
+            .relativize("./..", "../../../..")
             .relativize("a/b/c/d", "d")
+            .relativize("a/b/c/d/e", "d/e")
             .relativize("a/x", "../../x")
-            .relativize("x", "../../../x")
-            .relativize("", "../../..");
+            .relativize("../x", "../../../../x")
+            .relativizeFail("/")
+            .relativizeFail("/x");
         test("")
             .relativize("a", "a")
             .relativize("a/b/c", "a/b/c")
-            .relativize("", "");
+            .relativize("", "")
+            .relativize(".", ".")
+            .relativize("..", "..")
+            .relativize("../..", "../..")
+            .relativize("./..", "./..")     // ".." also valid
+            .relativizeFail("/")
+            .relativizeFail("/x");
+        test("..")
+            .relativize("../a", "a")
+            .relativize("..", "")
+            .relativize("./..", "")
+            .relativizeFail("/")
+            .relativizeFail("/x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("x");
+        test("../a")
+            .relativize("../a/b", "b")
+            .relativize("../a", "")
+            .relativize("..", "..")
+            .relativize("./..", "..")
+            .relativizeFail("/")
+            .relativizeFail("/x")
+            .relativizeFail("")
+            .relativizeFail(".")
+            .relativizeFail("x");
+        test("../a/b")
+            .relativize("../a/b/c", "c")
+            .relativize("../a/b", "")
+            .relativize("../a", "..")
+            .relativize("..", "../..")
+            .relativize("./..", "../..")
+            .relativizeFail("/")
+            .relativizeFail("/x")
+            .relativizeFail("")
+            .relativizeFail("x");
+        test("a/..")
+            .relativize("b", "b")
+            .relativize("", "")
+            .relativize(".", "")       // "." also valid
+            .relativize("..", "..")
+            .relativize("a/../b", "b")
+            .relativize("a/..", "")
+            .relativize("../b", "../b")
+            .relativize("b/..", "")
+            .relativizeFail("/")
+            .relativizeFail("/x");
+        test("a/../b")
+            .relativize("a/../b", "")
+            .relativize("a/..", "..")
+            .relativize("", "..")
+            .relativize(".", "..")
+            .relativize("..", "../..")
+            .relativize("b", "")
+            .relativize("c", "../c")
+            .relativize("../c", "../../c")
+            .relativize("a/../b/c", "c")
+            .relativizeFail("/")
+            .relativizeFail("/x");
 
         // normalize
         test("/")