Bug in Path's .relativize() documentation or am I doing something blatantly wrong here?

185 Views Asked by At

OK well, here is something fun. Note that "elements" here is an instance of PathElements, and for these .resolve() and .relativize() are fully tested and known to work... Links to the relevant method implementations Path.resolve(), Path.relativize()

Extract of the test method that fails:

/*
 * This test this part of the Path's .relativize() method:
 *
 * <p> For any two {@link #normalize normalized} paths <i>p</i> and
 * <i>q</i>, where <i>q</i> does not have a root component,
 * <blockquote>
 * <i>p</i><tt>.relativize(</tt><i>p</i><tt>.resolve(</tt><i>q</i><tt>))
 * .equals(</tt><i>q</i><tt>)</tt>
 * </blockquote>
 *
 * Unfortunately, that turns out NOT TO BE TRUE! Whether p is absolute or
 * relative, it is indeed the case that the path elements (root, names) are
 * the same but the filesystem DIFFERS.
 *
 * An as Path's .equals() requires that the two filesystems be equal in
 * order for two Paths to be equals, this contract can not be obeyed; or I
 * am doing something VERY wrong.
 */
@Test(enabled = false)
public void relativizeResolveRoundRobinWorks()
{
    /*
     * In order to set up the environment we define a mock
     * FileSystemProvider which both our mock filesystems will return when
     * .provider() is called.
     *
     * We also suppose that the same PathElementsFactory is used; while this
     * code is not written yet, there should be only one such factory per
     * FileSystemProvider anyway (which is fed into all generated FileSystem
     * instances -- at least that's the plan).
     *
     * Note that this test method assumes that .equals() and .hashCode() are
     * not implemented on GenericPath. As such we check that the FileSystem
     * is the same (this is required by Path's equals()) and that the path
     * elements are the same (this is this package's requirements).
     */
    final FileSystemProvider fsProvider = mock(FileSystemProvider.class);
    final PathElementsFactory elementsFactory
        = new UnixPathElementsFactory();
    final FileSystem fsForP = mock(FileSystem.class);
    final FileSystem fsForQ = mock(FileSystem.class);

    when(fsForP.provider()).thenReturn(fsProvider);
    when(fsForQ.provider()).thenReturn(fsProvider);

    /*
     * The path to be operated. As the contract says, it has no root
     * component.
     */
    final GenericPath q = new GenericPath(fsForQ, elementsFactory,
        new PathElements(null, new String[] { "q1", "q2" }));

    /*
     * The path against which both resolution and relativization are
     * performed. We take two versions of it: a non absolute one and an
     * absolute one.
     *
     * Note that since we use a UnixPathElementsFactory, we equate an
     * absolute path (or not) to a path which has a root component (or not).
     */
    GenericPath p;
    // "rr" as in "resolved, relativized"
    GenericPath rr;

    final CustomSoftAssertions soft = CustomSoftAssertions.create();

    /*
     * Try with the absolute version first...
     */
    p = new GenericPath(fsForP, elementsFactory,
        new PathElements("/", new String[] { "p1", "p2" }));
    rr = (GenericPath) p.relativize(p.resolve(q));

    soft.assertThat(rr.getFileSystem())
        .as("rr and q filesystems should be the same (p absolute)")
        .isSameAs(q.getFileSystem());
    soft.assertThat(rr.elements).hasSameContentsAs(q.elements);

    /*
     * Now with the non absolute version
     */
    p = new GenericPath(fsForP, elementsFactory,
        new PathElements(null, new String[] { "p1", "p2" }));
    rr = (GenericPath) p.relativize(p.resolve(q));

    soft.assertThat(rr.getFileSystem())
        .as("rr and q filesystems should be the same (p not absolute)")
        .isSameAs(q.getFileSystem());
    soft.assertThat(rr.elements).hasSameContentsAs(q.elements);

    soft.assertAll();
}

This test fails with:

org.assertj.core.api.SoftAssertionError: 
The following 2 assertions failed:
1) [rr and q filesystems should be the same (p absolute)] 
Expecting:
 <Mock for FileSystem, hashCode: 1125642929>
and actual:
 <Mock for FileSystem, hashCode: 1497261280>
to refer to the same object
2) [rr and q filesystems should be the same (p not absolute)] 
Expecting:
 <Mock for FileSystem, hashCode: 1125642929>
and actual:
 <Mock for FileSystem, hashCode: 1497261280>
to refer to the same object

And obviously so!

  • r = p.resolve(q) will give a Path which shares the same filesystem as p, not q;
  • therefore, p.relativize(r) will also give a Path with the same filesystem as p;
  • but the Path contract requires that in order for two paths to be equal, they share the same filesystem.

And this is always false in this scenario.

So, is this a blatant bug in the documentation or am I overlooking something?

1

There are 1 best solutions below

3
On

A: The documentation does not mention Paths from other filesystem.

The documentation for resolve and relativize is not perfectly clear here. But in case were the argument path is from a different filesystem resolve and relativize must throw ProviderMismatchException. Your test should throw earlier.

The constraints on a path can be quite different from filesystem to filesystem. Consider the relativize case. What should a path look like that can walk from one filesystem to another disjoint filesystem ?

Note: I tested several FileSystem implementations and all throw in that case.