From 7d2958374841fa6a278a940d7a6c5537c0b8bfbd Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Tue, 7 Feb 2017 13:11:01 -0800 Subject: [PATCH] Use platform agnostic web path implementation This implementation is nearly identical to a UnixPath class I wrote a couple years ago: - https://github.com/GoogleCloudPlatform/google-cloud-java/blob/01812a30f31151db6678280ab4969c5337f49d7a/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/UnixPath.java - https://github.com/GoogleCloudPlatform/google-cloud-java/blob/01812a30f31151db6678280ab4969c5337f49d7a/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/UnixPathTest.java ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=146824229 --- java/io/bazel/rules/closure/BUILD | 10 + java/io/bazel/rules/closure/Webpath.java | 510 +++++++++++++++ java/io/bazel/rules/closure/webfiles/BUILD | 7 +- .../closure/webfiles/WebfilesValidator.java | 1 + .../bazel/rules/closure/webfiles/Webpath.java | 106 --- .../bazel/rules/closure/webfiles/server/BUILD | 2 +- .../webfiles/server/WebfilesServer.java | 8 +- javatests/io/bazel/rules/closure/BUILD | 13 + .../io/bazel/rules/closure/WebpathTest.java | 613 ++++++++++++++++++ .../closure/webfiles/WebfilesTestSuite.java | 1 - .../rules/closure/webfiles/WebpathTest.java | 69 -- 11 files changed, 1154 insertions(+), 186 deletions(-) create mode 100644 java/io/bazel/rules/closure/Webpath.java delete mode 100644 java/io/bazel/rules/closure/webfiles/Webpath.java create mode 100644 javatests/io/bazel/rules/closure/WebpathTest.java delete mode 100644 javatests/io/bazel/rules/closure/webfiles/WebpathTest.java diff --git a/java/io/bazel/rules/closure/BUILD b/java/io/bazel/rules/closure/BUILD index 2d09e694a6..670570565c 100644 --- a/java/io/bazel/rules/closure/BUILD +++ b/java/io/bazel/rules/closure/BUILD @@ -42,6 +42,16 @@ java_library( deps = ["@com_google_guava"], ) +java_library( + name = "webpath", + srcs = ["Webpath.java"], + visibility = ["//visibility:public"], + deps = [ + "@com_google_code_findbugs_jsr305", + "@com_google_guava", + ], +) + java_proto_library( name = "build_info_java_proto", src = "build_info.proto", diff --git a/java/io/bazel/rules/closure/Webpath.java b/java/io/bazel/rules/closure/Webpath.java new file mode 100644 index 0000000000..1c093de73d --- /dev/null +++ b/java/io/bazel/rules/closure/Webpath.java @@ -0,0 +1,510 @@ +// Copyright 2016 The Closure Rules Authors. All Rights Reserved. +// +// Licensed 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 io.bazel.rules.closure; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import com.google.common.collect.Ordering; +import com.google.common.collect.PeekingIterator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * Web server path. + * + *

This class is a de facto implementation of the {@link java.nio.file.Path} API. That interface + * is not formally implemented because it would not be desirable to have web paths accidentally + * intermingle with file system paths. + * + *

This implementation is almost identical to {@code sun.nio.fs.UnixPath}. The main difference is + * that this implementation goes to greater lengths to preserve trailing slashes, since their + * presence has a distinct meaning in web applications. + * + *

Note: This code might not play nice with Supplementary + * Characters as Surrogates. + */ +@Immutable +public final class Webpath implements CharSequence, Comparable { + + public static final char DOT = '.'; + public static final char SEPARATOR = '/'; + public static final String ROOT = "/"; + public static final String CURRENT_DIR = "."; + public static final String PARENT_DIR = ".."; + public static final Webpath EMPTY_PATH = new Webpath(""); + public static final Webpath ROOT_PATH = new Webpath(ROOT); + + private static final Splitter SPLITTER = Splitter.on(SEPARATOR).omitEmptyStrings(); + private static final Joiner JOINER = Joiner.on(SEPARATOR); + private static final Ordering> ORDERING = Ordering.natural().lexicographical(); + + private final String path; + private List lazyStringParts; + + private Webpath(String path) { + this.path = checkNotNull(path); + } + + /** Returns new path of {@code first}. */ + public static Webpath get(String path) { + if (path.isEmpty()) { + return EMPTY_PATH; + } else if (isRootInternal(path)) { + return ROOT_PATH; + } + return new Webpath(path); + } + + /** Returns {@code true} consists only of {@code separator}. */ + public boolean isRoot() { + return isRootInternal(path); + } + + private static boolean isRootInternal(String path) { + return path.length() == 1 && path.charAt(0) == SEPARATOR; + } + + /** Returns {@code true} if path starts with {@code separator}. */ + public boolean isAbsolute() { + return isAbsoluteInternal(path); + } + + private static boolean isAbsoluteInternal(String path) { + return !path.isEmpty() && path.charAt(0) == SEPARATOR; + } + + /** Returns {@code true} if path ends with {@code separator}. */ + public boolean hasTrailingSeparator() { + return hasTrailingSeparatorInternal(path); + } + + private static boolean hasTrailingSeparatorInternal(CharSequence path) { + return path.length() != 0 && path.charAt(path.length() - 1) == SEPARATOR; + } + + /** Returns {@code true} if path ends with a trailing slash, or would after normalization. */ + public boolean seemsLikeADirectory() { + int length = path.length(); + return path.isEmpty() + || path.charAt(length - 1) == SEPARATOR + || (path.endsWith(".") && (length == 1 || path.charAt(length - 2) == SEPARATOR)) + || (path.endsWith("..") && (length == 2 || path.charAt(length - 3) == SEPARATOR)); + } + + /** + * Returns last component in {@code path}. + * + * @see java.nio.file.Path#getFileName() + */ + @Nullable + public Webpath getFileName() { + if (path.isEmpty()) { + return EMPTY_PATH; + } else if (isRoot()) { + return null; + } else { + List parts = getParts(); + String last = parts.get(parts.size() - 1); + return parts.size() == 1 && path.equals(last) ? this : new Webpath(last); + } + } + + /** + * Returns parent directory (including trailing separator) or {@code null} if no parent remains. + * + * @see java.nio.file.Path#getParent() + */ + @Nullable + public Webpath getParent() { + if (path.isEmpty() || isRoot()) { + return null; + } + int index = + hasTrailingSeparator() + ? path.lastIndexOf(SEPARATOR, path.length() - 2) + : path.lastIndexOf(SEPARATOR); + if (index == -1) { + return isAbsolute() ? ROOT_PATH : null; + } else { + return new Webpath(path.substring(0, index + 1)); + } + } + + /** + * Returns root component if an absolute path, otherwise {@code null}. + * + * @see java.nio.file.Path#getRoot() + */ + @Nullable + public Webpath getRoot() { + return isAbsolute() ? ROOT_PATH : null; + } + + /** + * Returns specified range of sub-components in path joined together. + * + * @see java.nio.file.Path#subpath(int, int) + */ + public Webpath subpath(int beginIndex, int endIndex) { + if (path.isEmpty() && beginIndex == 0 && endIndex == 1) { + return this; + } + checkArgument(beginIndex >= 0 && endIndex > beginIndex); + List subList; + try { + subList = getParts().subList(beginIndex, endIndex); + } catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException(); + } + return new Webpath(JOINER.join(subList)); + } + + /** + * Returns number of components in {@code path}. + * + * @see java.nio.file.Path#getNameCount() + */ + public int getNameCount() { + if (path.isEmpty()) { + return 1; + } else if (isRoot()) { + return 0; + } else { + return getParts().size(); + } + } + + /** + * Returns component in {@code path} at {@code index}. + * + * @see java.nio.file.Path#getName(int) + */ + public Webpath getName(int index) { + if (path.isEmpty()) { + checkArgument(index == 0); + return this; + } + try { + return new Webpath(getParts().get(index)); + } catch (IndexOutOfBoundsException e) { + throw new IllegalArgumentException(); + } + } + + /** + * Returns path without extra separators or {@code .} and {@code ..}, preserving trailing slash. + * + * @see java.nio.file.Path#normalize() + */ + public Webpath normalize() { + List parts = new ArrayList<>(); + boolean mutated = false; + int resultLength = 0; + int mark = 0; + int index; + do { + index = path.indexOf(SEPARATOR, mark); + String part = path.substring(mark, index == -1 ? path.length() : index + 1); + switch (part) { + case CURRENT_DIR: + case CURRENT_DIR + SEPARATOR: + if (!parts.isEmpty()) { + if (parts.get(parts.size() - 1).equals(CURRENT_DIR + SEPARATOR)) { + resultLength -= parts.remove(parts.size() - 1).length(); + } + mutated = true; + break; + } + // fallthrough + case PARENT_DIR: + case PARENT_DIR + SEPARATOR: + if (!parts.isEmpty()) { + if (parts.size() == 1 && parts.get(0).equals(ROOT)) { + mutated = true; + break; + } + String last = parts.get(parts.size() - 1); + if (last.equals(CURRENT_DIR + SEPARATOR)) { + resultLength -= parts.remove(parts.size() - 1).length(); + mutated = true; + } else if (!last.equals(PARENT_DIR + SEPARATOR)) { + resultLength -= parts.remove(parts.size() - 1).length(); + mutated = true; + break; + } + } + // fallthrough + default: + if (index != mark || index == 0) { + parts.add(part); + resultLength = part.length(); + } else { + mutated = true; + } + } + mark = index + 1; + } while (index != -1); + if (!mutated) { + return this; + } + StringBuilder result = new StringBuilder(resultLength); + for (String part : parts) { + result.append(part); + } + return new Webpath(result.toString()); + } + + /** + * Returns {@code other} appended to {@code path}. + * + * @see java.nio.file.Path#resolve(java.nio.file.Path) + */ + public Webpath resolve(Webpath other) { + if (other.path.isEmpty()) { + return this; + } else if (other.isAbsolute()) { + return other; + } else if (hasTrailingSeparator()) { + return new Webpath(path + other.path); + } else { + return new Webpath(path + SEPARATOR + other.path); + } + } + + /** + * Returns {@code other} resolved against parent of {@code path}. + * + * @see java.nio.file.Path#resolveSibling(java.nio.file.Path) + */ + public Webpath resolveSibling(Webpath other) { + checkNotNull(other); + Webpath parent = getParent(); + return parent == null ? other : parent.resolve(other); + } + + /** Returns absolute path of {@code reference} relative to {@code file}. */ + public Webpath lookup(Webpath reference) { + return getParent().resolve(reference).normalize(); + } + + /** + * Returns {@code other} made relative to {@code path}. + * + * @see java.nio.file.Path#relativize(java.nio.file.Path) + */ + public Webpath relativize(Webpath other) { + checkArgument(isAbsolute() == other.isAbsolute(), "'other' is different type of Path"); + if (path.isEmpty()) { + return other; + } + PeekingIterator left = Iterators.peekingIterator(split()); + PeekingIterator right = Iterators.peekingIterator(other.split()); + while (left.hasNext() && right.hasNext()) { + if (!left.peek().equals(right.peek())) { + break; + } + left.next(); + right.next(); + } + StringBuilder result = new StringBuilder(path.length() + other.path.length()); + while (left.hasNext()) { + result.append(PARENT_DIR); + result.append(SEPARATOR); + left.next(); + } + while (right.hasNext()) { + result.append(right.next()); + result.append(SEPARATOR); + } + if (result.length() > 0 && !other.hasTrailingSeparator()) { + result.deleteCharAt(result.length() - 1); + } + return new Webpath(result.toString()); + } + + /** + * Returns {@code true} if {@code path} starts with {@code other}. + * + * @see java.nio.file.Path#startsWith(java.nio.file.Path) + */ + public boolean startsWith(Webpath other) { + Webpath me = removeTrailingSeparator(); + other = other.removeTrailingSeparator(); + if (other.path.length() > me.path.length()) { + return false; + } else if (me.isAbsolute() != other.isAbsolute()) { + return false; + } else if (!me.path.isEmpty() && other.path.isEmpty()) { + return false; + } + return startsWith(split(), other.split()); + } + + private static boolean startsWith(Iterator lefts, Iterator rights) { + while (rights.hasNext()) { + if (!lefts.hasNext() || !rights.next().equals(lefts.next())) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} if {@code path} ends with {@code other}. + * + * @see java.nio.file.Path#endsWith(java.nio.file.Path) + */ + public boolean endsWith(Webpath other) { + Webpath me = removeTrailingSeparator(); + other = other.removeTrailingSeparator(); + if (other.path.length() > me.path.length()) { + return false; + } else if (!me.path.isEmpty() && other.path.isEmpty()) { + return false; + } else if (other.isAbsolute()) { + return me.isAbsolute() && me.path.equals(other.path); + } + return startsWith(me.splitReverse(), other.splitReverse()); + } + + /** + * Compares two paths lexicographically for ordering. + * + * @see java.nio.file.Path#compareTo(java.nio.file.Path) + */ + @Override + public int compareTo(Webpath other) { + if (isAbsolute()) { + if (!other.isAbsolute()) { + return 1; + } + } else { + if (other.isAbsolute()) { + return -1; + } + } + int result = ORDERING.compare(getParts(), other.getParts()); + if (result == 0) { + if (hasTrailingSeparator()) { + if (!other.hasTrailingSeparator()) { + return 1; + } + } else { + if (other.hasTrailingSeparator()) { + return -1; + } + } + } + return result; + } + + /** Converts relative path to an absolute path. */ + public Webpath toAbsolutePath(Webpath currentWorkingDirectory) { + checkArgument(currentWorkingDirectory.isAbsolute()); + return isAbsolute() ? this : currentWorkingDirectory.resolve(this); + } + + /** Returns {@code toAbsolutePath(ROOT_PATH)}. */ + public Webpath toAbsolutePath() { + return toAbsolutePath(ROOT_PATH); + } + + /** Removes beginning separator from path, if an absolute path. */ + public Webpath removeBeginningSeparator() { + return isAbsolute() ? new Webpath(path.substring(1)) : this; + } + + /** Adds trailing separator to path, if it isn't present. */ + public Webpath addTrailingSeparator() { + return hasTrailingSeparator() ? this : new Webpath(path + SEPARATOR); + } + + /** Removes trailing separator from path, unless it's root. */ + public Webpath removeTrailingSeparator() { + if (!isRoot() && hasTrailingSeparator()) { + return new Webpath(path.substring(0, path.length() - 1)); + } else { + return this; + } + } + + /** Splits path into components, excluding separators and empty strings. */ + public Iterator split() { + return getParts().iterator(); + } + + /** Splits path into components in reverse, excluding separators and empty strings. */ + public Iterator splitReverse() { + return Lists.reverse(getParts()).iterator(); + } + + @Override + public boolean equals(Object other) { + return this == other || (other instanceof Webpath && compareTo((Webpath) other) == 0); + } + + @Override + public int hashCode() { + return Objects.hash(getParts(), isAbsolute(), hasTrailingSeparator()); + } + + /** Returns path as a string. */ + @Override + public String toString() { + return path; + } + + @Override + public int length() { + return path.length(); + } + + @Override + public char charAt(int index) { + return path.charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + return path.subSequence(start, end); + } + + /** Returns {@code true} if this path is an empty string. */ + public boolean isEmpty() { + return path.isEmpty(); + } + + /** Returns list of path components, excluding slashes. */ + private List getParts() { + List result = lazyStringParts; + return result != null + ? result + : (lazyStringParts = + path.isEmpty() || isRoot() + ? Collections.emptyList() + : SPLITTER.splitToList(path)); + } +} diff --git a/java/io/bazel/rules/closure/webfiles/BUILD b/java/io/bazel/rules/closure/webfiles/BUILD index b2be9973fe..3e45af2d8f 100644 --- a/java/io/bazel/rules/closure/webfiles/BUILD +++ b/java/io/bazel/rules/closure/webfiles/BUILD @@ -21,15 +21,12 @@ java_library( srcs = [ "WebfilesValidator.java", "WebfilesValidatorProgram.java", - "Webpath.java", - ], - visibility = [ - "//java/io/bazel/rules/closure:__subpackages__", - "//javatests/io/bazel/rules/closure/webfiles:__pkg__", ], + visibility = ["//:__subpackages__"], deps = [ ":build_info_java_proto", "//java/io/bazel/rules/closure:tarjan", + "//java/io/bazel/rules/closure:webpath", "//java/io/bazel/rules/closure/program", "@com_google_closure_stylesheets", "@com_google_dagger", diff --git a/java/io/bazel/rules/closure/webfiles/WebfilesValidator.java b/java/io/bazel/rules/closure/webfiles/WebfilesValidator.java index ac53843138..00f9bcda4f 100644 --- a/java/io/bazel/rules/closure/webfiles/WebfilesValidator.java +++ b/java/io/bazel/rules/closure/webfiles/WebfilesValidator.java @@ -36,6 +36,7 @@ import com.google.common.css.compiler.ast.GssParserException; import com.google.common.css.compiler.passes.PassRunner; import io.bazel.rules.closure.Tarjan; +import io.bazel.rules.closure.Webpath; import io.bazel.rules.closure.webfiles.BuildInfo.Webfiles; import io.bazel.rules.closure.webfiles.BuildInfo.WebfilesSource; import java.io.IOException; diff --git a/java/io/bazel/rules/closure/webfiles/Webpath.java b/java/io/bazel/rules/closure/webfiles/Webpath.java deleted file mode 100644 index 315f028d7f..0000000000 --- a/java/io/bazel/rules/closure/webfiles/Webpath.java +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2016 The Closure Rules Authors. All Rights Reserved. -// -// Licensed 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 io.bazel.rules.closure.webfiles; - -import java.nio.file.Path; -import java.nio.file.Paths; - -/** - * Web server path. - * - *

This is a delegate for {@link Path} which implements the label granularity we want. We could - * have just used the {@code Path} class for web server paths too, but then the type wouldn't be - * distinguishable, thereby harming readability. - */ -public final class Webpath implements Comparable { - - /** Creates new instance. */ - public static Webpath get(String path) { - return new Webpath(Paths.get(path)); - } - - private final Path path; - - private Webpath(Path path) { - this.path = path; - } - - /** Returns encapsulated path object. */ - public Path getPath() { - return path; - } - - /** Returns absolute path of {@code reference} relative to {@code file}. */ - public Webpath lookup(Webpath reference) { - return getParent().resolve(reference).normalize(); - } - - /** Delegates to {@link Path#isAbsolute()}. */ - public boolean isAbsolute() { - return path.isAbsolute(); - } - - /** Delegates to {@link Path#getParent()}. */ - public Webpath getParent() { - return new Webpath(path.getParent()); - } - - /** Delegates to {@link Path#normalize()}. */ - public Webpath normalize() { - return new Webpath(path.normalize()); - } - - /** Delegates to {@link Path#resolve(Path)}. */ - public Webpath resolve(Webpath other) { - return new Webpath(path.resolve(other.path)); - } - - /** Delegates to {@link Path#startsWith(Path)}. */ - public boolean startsWith(Webpath other) { - return path.startsWith(other.path); - } - - /** Delegates to {@link Path#getNameCount()}. */ - public int getNameCount() { - return path.getNameCount(); - } - - /** Delegates to {@link Path#subpath(int, int)}. */ - public Webpath subpath(int start, int end) { - return new Webpath(path.subpath(start, end)); - } - - @Override - public int hashCode() { - return path.hashCode(); - } - - @Override - public boolean equals(Object other) { - return this == other - || other instanceof Webpath - && path.equals(((Webpath) other).path); - } - - @Override - public int compareTo(Webpath other) { - return path.compareTo(other.path); - } - - @Override - public String toString() { - return path.toString(); - } -} diff --git a/java/io/bazel/rules/closure/webfiles/server/BUILD b/java/io/bazel/rules/closure/webfiles/server/BUILD index 3e5a90e7a0..614ba90f5a 100644 --- a/java/io/bazel/rules/closure/webfiles/server/BUILD +++ b/java/io/bazel/rules/closure/webfiles/server/BUILD @@ -29,7 +29,7 @@ java_binary( deps = [ ":listing", "//closure/templates", - "//java/io/bazel/rules/closure/webfiles", + "//java/io/bazel/rules/closure:webpath", "//java/io/bazel/rules/closure/webfiles:build_info_java_proto", "@com_google_code_findbugs_jsr305", "@com_google_guava", diff --git a/java/io/bazel/rules/closure/webfiles/server/WebfilesServer.java b/java/io/bazel/rules/closure/webfiles/server/WebfilesServer.java index 09bf944180..950ecf2c31 100644 --- a/java/io/bazel/rules/closure/webfiles/server/WebfilesServer.java +++ b/java/io/bazel/rules/closure/webfiles/server/WebfilesServer.java @@ -24,7 +24,6 @@ import com.google.common.base.Functions; import com.google.common.base.Predicate; -import com.google.common.base.Splitter; import com.google.common.collect.FluentIterable; import com.google.common.collect.Iterators; import com.google.common.io.Resources; @@ -35,9 +34,9 @@ import com.google.template.soy.data.SoyListData; import com.google.template.soy.data.SoyMapData; import com.google.template.soy.tofu.SoyTofu; +import io.bazel.rules.closure.Webpath; import io.bazel.rules.closure.webfiles.BuildInfo.Webfiles; import io.bazel.rules.closure.webfiles.BuildInfo.WebfilesSource; -import io.bazel.rules.closure.webfiles.Webpath; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.BindException; @@ -72,7 +71,6 @@ public final class WebfilesServer extends HttpServlet { private static final Path TEST_SRCDIR = Paths.get(".."); private static final Webpath RUNFILES_PREFIX = Webpath.get("/_/runfiles"); private static final MediaType DEFAULT_MIME_TYPE = MediaType.OCTET_STREAM; - private static final Splitter TAB_SPLITTER = Splitter.on('\t'); private static final Pattern ALLOWS_GZIP = Pattern.compile("(?:^|,|\\s)(?:(?:x-)?gzip|\\*)(?!;q=0)(?:\\s|,|$)"); private static final SoyTofu TOFU = @@ -185,7 +183,9 @@ void handle() throws IOException { return; } if (webpath.startsWith(RUNFILES_PREFIX)) { - path = webpath.subpath(RUNFILES_PREFIX.getNameCount(), webpath.getNameCount()).getPath(); + path = + Paths.get( + webpath.subpath(RUNFILES_PREFIX.getNameCount(), webpath.getNameCount()).toString()); verify(!path.isAbsolute()); path = TEST_SRCDIR.resolve(path); if (Files.exists(path)) { diff --git a/javatests/io/bazel/rules/closure/BUILD b/javatests/io/bazel/rules/closure/BUILD index 2ed80c1235..83ab5cd3d4 100644 --- a/javatests/io/bazel/rules/closure/BUILD +++ b/javatests/io/bazel/rules/closure/BUILD @@ -24,3 +24,16 @@ java_test( "@junit", ], ) + +java_test( + name = "WebpathTest", + srcs = ["WebpathTest.java"], + deps = [ + "//java/io/bazel/rules/closure:webpath", + "@com_google_guava", + "@com_google_guava_testlib", + "@com_google_jimfs", + "@com_google_truth", + "@junit", + ], +) diff --git a/javatests/io/bazel/rules/closure/WebpathTest.java b/javatests/io/bazel/rules/closure/WebpathTest.java new file mode 100644 index 0000000000..3f7b1efa75 --- /dev/null +++ b/javatests/io/bazel/rules/closure/WebpathTest.java @@ -0,0 +1,613 @@ +// Copyright 2017 The Closure Rules Authors. All Rights Reserved. +// +// Licensed 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 io.bazel.rules.closure; + +import static org.junit.Assume.assumeTrue; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterators; +import com.google.common.collect.Ordering; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import com.google.common.testing.EqualsTester; +import com.google.common.testing.NullPointerTester; +import com.google.common.truth.BooleanSubject; +import com.google.common.truth.DefaultSubject; +import com.google.common.truth.IterableSubject; +import com.google.common.truth.Subject; +import com.google.common.truth.Truth; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link Webpath}. */ +@RunWith(JUnit4.class) +public class WebpathTest { + + @Rule public final ExpectedException thrown = ExpectedException.none(); + + public final FileSystem unix = Jimfs.newFileSystem(Configuration.unix()); + + @After + public void after() throws Exception { + unix.close(); + } + + private static Webpath wp(String path) { + return Webpath.get(path); + } + + private Path xp(String path) { + return unix.getPath(path); + } + + // Workaround fact that Path interface matches multiple assertThat() method overloads. + private static Subject assertThat(Object subject) { + return Truth.assertThat(subject); + } + + private static BooleanSubject assertThat(boolean subject) { + return Truth.assertThat(subject); + } + + private static IterableSubject assertThat(ImmutableList subject) { + return Truth.assertThat(subject); + } + + @Test + public void testNormalize_relativeDot_remainsDot() { + assertThat(xp(".").normalize()).isEqualTo(xp(".")); + assertThat(wp(".").normalize()).isEqualTo(wp(".")); + } + + @Test + public void testNormalize_relativeDot_eliminatesItselfForSomeStrangeReason() { + assertThat(xp("./.").normalize()).isEqualTo(xp("")); + assertThat(wp("./.").normalize()).isEqualTo(wp("")); + } + + @Test + public void testNormalize_root_remainsRoot() { + assertThat(xp("/").normalize()).isEqualTo(xp("/")); + assertThat(wp("/").normalize()).isEqualTo(wp("/")); + } + + @Test + public void testNormalize_rootDot_becomesRoot() { + assertThat(xp("/.").normalize()).isEqualTo(xp("/")); + assertThat(wp("/.").normalize()).isEqualTo(wp("/")); + } + + @Test + public void testNormalize_parentOfRoot_isRoot() { + assertThat(xp("/..").normalize()).isEqualTo(xp("/")); + assertThat(wp("/..").normalize()).isEqualTo(wp("/")); + assertThat(xp("/../..").normalize()).isEqualTo(xp("/")); + assertThat(wp("/../..").normalize()).isEqualTo(wp("/")); + assertThat(xp("/hi/../..").normalize()).isEqualTo(xp("/")); + assertThat(wp("/hi/../..").normalize()).isEqualTo(wp("/")); + } + + @Test + public void testNormalize_relativeDoubleDot_remainsDoubleDot() { + assertThat(xp("..").normalize()).isEqualTo(xp("..")); + assertThat(wp("..").normalize()).isEqualTo(wp("..")); + assertThat(xp("../..").normalize()).isEqualTo(xp("../..")); + assertThat(wp("../..").normalize()).isEqualTo(wp("../..")); + assertThat(xp(".././..").normalize()).isEqualTo(xp("../..")); + assertThat(wp(".././..").normalize()).isEqualTo(wp("../..")); + assertThat(xp("hi/../..").normalize()).isEqualTo(xp("..")); + assertThat(wp("hi/../..").normalize()).isEqualTo(wp("..")); + assertThat(xp("../").normalize()).isEqualTo(xp("../")); + assertThat(wp("../").normalize()).isEqualTo(wp("../")); + } + + @Test + public void testNormalize_doubleDot_trumpsSingleDot() { + assertThat(xp("././..").normalize()).isEqualTo(xp("..")); + assertThat(wp("././..").normalize()).isEqualTo(wp("..")); + } + + @Test + public void testNormalize_midPath_isConsistentWithUnix() { + assertThat(xp("/a/b/../c").normalize()).isEqualTo(xp("/a/c")); + assertThat(wp("/a/b/../c").normalize()).isEqualTo(wp("/a/c")); + assertThat(xp("/a/b/./c").normalize()).isEqualTo(xp("/a/b/c")); + assertThat(wp("/a/b/./c").normalize()).isEqualTo(wp("/a/b/c")); + assertThat(xp("a/b/../c").normalize()).isEqualTo(xp("a/c")); + assertThat(wp("a/b/../c").normalize()).isEqualTo(wp("a/c")); + assertThat(xp("a/b/./c").normalize()).isEqualTo(xp("a/b/c")); + assertThat(wp("a/b/./c").normalize()).isEqualTo(wp("a/b/c")); + assertThat(xp("/a/b/../../c").normalize()).isEqualTo(xp("/c")); + assertThat(wp("/a/b/../../c").normalize()).isEqualTo(wp("/c")); + assertThat(xp("/a/b/./.././.././c").normalize()).isEqualTo(xp("/c")); + assertThat(wp("/a/b/./.././.././c").normalize()).isEqualTo(wp("/c")); + } + + @Test + public void testNormalize_parentDir_preservesTrailingSlashOnGuaranteedDirectories() { + assertThat(xp("hi/there/..").normalize()).isEqualTo(xp("hi/")); + assertThat(wp("hi/there/..").normalize()).isEqualTo(wp("hi/")); + assertThat(xp("hi/there/../").normalize()).isEqualTo(xp("hi/")); + assertThat(wp("hi/there/../").normalize()).isEqualTo(wp("hi/")); + } + + @Test + public void testNormalize_empty_returnsEmpty() { + assertThat(xp("").normalize()).isEqualTo(xp("")); + assertThat(wp("").normalize()).isEqualTo(wp("")); + } + + @Test + public void testNormalize_extraSlashes_getRemoved() { + assertThat(xp("///").normalize()).isEqualTo(xp("/")); + assertThat(wp("///").normalize()).isEqualTo(wp("/")); + assertThat(xp("/hi//there").normalize()).isEqualTo(xp("/hi/there")); + assertThat(wp("/hi//there").normalize()).isEqualTo(wp("/hi/there")); + assertThat(xp("/hi////.///there").normalize()).isEqualTo(xp("/hi/there")); + assertThat(wp("/hi////.///there").normalize()).isEqualTo(wp("/hi/there")); + } + + @Test + public void testNormalize_preservesTrailingSlash() { + assertThat(xp("/hi/").normalize()).isEqualTo(xp("/hi/")); + assertThat(wp("/hi/").normalize()).isEqualTo(wp("/hi/")); + assertThat(xp("/hi///").normalize()).isEqualTo(xp("/hi/")); + assertThat(wp("/hi///").normalize()).isEqualTo(wp("/hi/")); + } + + @Test + public void testNormalize_outputIsEqual_newObjectIsntCreated() { + Webpath path = wp("/hi/there"); + assertThat(path.normalize()).isSameAs(path); + path = wp("/hi/there/"); + assertThat(path.normalize()).isSameAs(path); + path = wp("../"); + assertThat(path.normalize()).isSameAs(path); + path = wp("../.."); + assertThat(path.normalize()).isSameAs(path); + path = wp("./"); + assertThat(path.normalize()).isSameAs(path); + } + + @Test + public void testResolve() { + assertThat(xp("/hello").resolve(xp("cat"))).isEqualTo(xp("/hello/cat")); + assertThat(wp("/hello").resolve(wp("cat"))).isEqualTo(wp("/hello/cat")); + assertThat(xp("/hello/").resolve(xp("cat"))).isEqualTo(xp("/hello/cat")); + assertThat(wp("/hello/").resolve(wp("cat"))).isEqualTo(wp("/hello/cat")); + assertThat(xp("hello/").resolve(xp("cat"))).isEqualTo(xp("hello/cat")); + assertThat(wp("hello/").resolve(wp("cat"))).isEqualTo(wp("hello/cat")); + assertThat(xp("hello/").resolve(xp("cat/"))).isEqualTo(xp("hello/cat/")); + assertThat(wp("hello/").resolve(wp("cat/"))).isEqualTo(wp("hello/cat/")); + assertThat(xp("hello/").resolve(xp(""))).isEqualTo(xp("hello/")); + assertThat(wp("hello/").resolve(wp(""))).isEqualTo(wp("hello/")); + assertThat(xp("hello/").resolve(xp("/hi/there"))).isEqualTo(xp("/hi/there")); + assertThat(wp("hello/").resolve(wp("/hi/there"))).isEqualTo(wp("/hi/there")); + } + + @Test + public void testResolve_sameObjectOptimization() { + Webpath path = wp("/hi/there"); + assertThat(path.resolve(wp(""))).isSameAs(path); + assertThat(wp("hello").resolve(path)).isSameAs(path); + } + + @Test + public void testResolveSibling() { + assertThat(xp("/hello/cat").resolveSibling(xp("dog"))).isEqualTo(xp("/hello/dog")); + assertThat(wp("/hello/cat").resolveSibling(wp("dog"))).isEqualTo(wp("/hello/dog")); + assertThat(xp("/").resolveSibling(xp("dog"))).isEqualTo(xp("dog")); + assertThat(wp("/").resolveSibling(wp("dog"))).isEqualTo(wp("dog")); + } + + @Test + public void testRelativize() { + assertThat(wp("/foo/bar/hop/dog").relativize(wp("/foo/mop/top"))) + .isEqualTo(wp("../../../mop/top")); + assertThat(wp("/foo/bar/dog").relativize(wp("/foo/mop/top"))).isEqualTo(wp("../../mop/top")); + assertThat(wp("/foo/bar/hop/dog").relativize(wp("/foo/mop/top/../../mog"))) + .isEqualTo(wp("../../../mop/top/../../mog")); + assertThat(wp("/foo/bar/hop/dog").relativize(wp("/foo/../mog"))) + .isEqualTo(wp("../../../../mog")); + assertThat(wp("").relativize(wp("foo/mop/top/"))).isEqualTo(wp("foo/mop/top/")); + } + + @Test + public void testRelativize_absoluteMismatch_notAllowed() { + thrown.expect(IllegalArgumentException.class); + wp("/a/b/").relativize(wp("")); + } + + @Test + public void testRelativize_preservesTrailingSlash() { + // This behavior actually diverges from sun.nio.fs.UnixPath: + // bsh % print(Paths.get("/a/b/").relativize(Paths.get("/etc/"))); + // ../../etc + assertThat(wp("/foo/bar/hop/dog").relativize(wp("/foo/../mog/"))) + .isEqualTo(wp("../../../../mog/")); + assertThat(wp("/a/b/").relativize(wp("/etc/"))).isEqualTo(wp("../../etc/")); + } + + @Test + public void testStartsWith() { + assertThat(xp("/hi/there").startsWith(xp("/hi/there"))).isTrue(); + assertThat(wp("/hi/there").startsWith(wp("/hi/there"))).isTrue(); + assertThat(xp("/hi/there").startsWith(xp("/hi/therf"))).isFalse(); + assertThat(wp("/hi/there").startsWith(wp("/hi/therf"))).isFalse(); + assertThat(xp("/hi/there").startsWith(xp("/hi"))).isTrue(); + assertThat(wp("/hi/there").startsWith(wp("/hi"))).isTrue(); + assertThat(xp("/hi/there").startsWith(xp("/hi/"))).isTrue(); + assertThat(wp("/hi/there").startsWith(wp("/hi/"))).isTrue(); + assertThat(xp("/hi/there").startsWith(xp("hi"))).isFalse(); + assertThat(wp("/hi/there").startsWith(wp("hi"))).isFalse(); + assertThat(xp("/hi/there").startsWith(xp("/"))).isTrue(); + assertThat(wp("/hi/there").startsWith(wp("/"))).isTrue(); + assertThat(xp("/hi/there").startsWith(xp(""))).isFalse(); + assertThat(wp("/hi/there").startsWith(wp(""))).isFalse(); + assertThat(xp("/a/b").startsWith(xp("a/b/"))).isFalse(); + assertThat(wp("/a/b").startsWith(wp("a/b/"))).isFalse(); + assertThat(xp("/a/b/").startsWith(xp("a/b/"))).isFalse(); + assertThat(wp("/a/b/").startsWith(wp("a/b/"))).isFalse(); + assertThat(xp("/hi/there").startsWith(xp(""))).isFalse(); + assertThat(wp("/hi/there").startsWith(wp(""))).isFalse(); + assertThat(xp("").startsWith(xp(""))).isTrue(); + assertThat(wp("").startsWith(wp(""))).isTrue(); + } + + @Test + public void testStartsWith_comparesComponentsIndividually() { + assertThat(xp("/hello").startsWith(xp("/hell"))).isFalse(); + assertThat(wp("/hello").startsWith(wp("/hell"))).isFalse(); + assertThat(xp("/hello").startsWith(xp("/hello"))).isTrue(); + assertThat(wp("/hello").startsWith(wp("/hello"))).isTrue(); + } + + @Test + public void testEndsWith() { + assertThat(xp("/hi/there").endsWith(xp("there"))).isTrue(); + assertThat(wp("/hi/there").endsWith(wp("there"))).isTrue(); + assertThat(xp("/hi/there").endsWith(xp("therf"))).isFalse(); + assertThat(wp("/hi/there").endsWith(wp("therf"))).isFalse(); + assertThat(xp("/hi/there").endsWith(xp("/blag/therf"))).isFalse(); + assertThat(wp("/hi/there").endsWith(wp("/blag/therf"))).isFalse(); + assertThat(xp("/hi/there").endsWith(xp("/hi/there"))).isTrue(); + assertThat(wp("/hi/there").endsWith(wp("/hi/there"))).isTrue(); + assertThat(xp("/hi/there").endsWith(xp("/there"))).isFalse(); + assertThat(wp("/hi/there").endsWith(wp("/there"))).isFalse(); + assertThat(xp("/human/that/you/cry").endsWith(xp("that/you/cry"))).isTrue(); + assertThat(wp("/human/that/you/cry").endsWith(wp("that/you/cry"))).isTrue(); + assertThat(xp("/human/that/you/cry").endsWith(xp("that/you/cry/"))).isTrue(); + assertThat(wp("/human/that/you/cry").endsWith(wp("that/you/cry/"))).isTrue(); + assertThat(xp("/hi/there/").endsWith(xp("/"))).isFalse(); + assertThat(wp("/hi/there/").endsWith(wp("/"))).isFalse(); + assertThat(xp("/hi/there").endsWith(xp(""))).isFalse(); + assertThat(wp("/hi/there").endsWith(wp(""))).isFalse(); + assertThat(xp("").endsWith(xp(""))).isTrue(); + assertThat(wp("").endsWith(wp(""))).isTrue(); + } + + @Test + public void testEndsWith_comparesComponentsIndividually() { + assertThat(xp("/hello").endsWith(xp("lo"))).isFalse(); + assertThat(wp("/hello").endsWith(wp("lo"))).isFalse(); + assertThat(xp("/hello").endsWith(xp("hello"))).isTrue(); + assertThat(wp("/hello").endsWith(wp("hello"))).isTrue(); + } + + @Test + public void testGetParent() { + assertThat(xp("").getParent()).isNull(); + assertThat(wp("").getParent()).isNull(); + assertThat(xp("/").getParent()).isNull(); + assertThat(wp("/").getParent()).isNull(); + assertThat(xp("aaa/").getParent()).isNull(); + assertThat(wp("aaa/").getParent()).isNull(); + assertThat(xp("aaa").getParent()).isNull(); + assertThat(wp("aaa").getParent()).isNull(); + assertThat(xp("/aaa/").getParent()).isEqualTo(xp("/")); + assertThat(wp("/aaa/").getParent()).isEqualTo(wp("/")); + assertThat(xp("a/b/c").getParent()).isEqualTo(xp("a/b/")); + assertThat(wp("a/b/c").getParent()).isEqualTo(wp("a/b/")); + assertThat(xp("a/b/c/").getParent()).isEqualTo(xp("a/b/")); + assertThat(wp("a/b/c/").getParent()).isEqualTo(wp("a/b/")); + assertThat(xp("a/b/").getParent()).isEqualTo(xp("a/")); + assertThat(wp("a/b/").getParent()).isEqualTo(wp("a/")); + } + + @Test + public void testGetRoot() { + assertThat(xp("/hello").getRoot()).isEqualTo(xp("/")); + assertThat(wp("/hello").getRoot()).isEqualTo(wp("/")); + assertThat(xp("hello").getRoot()).isNull(); + assertThat(wp("hello").getRoot()).isNull(); + assertThat(xp("/hello/friend").getRoot()).isEqualTo(xp("/")); + assertThat(wp("/hello/friend").getRoot()).isEqualTo(wp("/")); + assertThat(xp("hello/friend").getRoot()).isNull(); + assertThat(wp("hello/friend").getRoot()).isNull(); + } + + @Test + public void testGetFileName() { + assertThat(xp("").getFileName()).isEqualTo(xp("")); + assertThat(wp("").getFileName()).isEqualTo(wp("")); + assertThat(xp("/").getFileName()).isNull(); + assertThat(wp("/").getFileName()).isNull(); + assertThat(xp("/dark").getFileName()).isEqualTo(xp("dark")); + assertThat(wp("/dark").getFileName()).isEqualTo(wp("dark")); + assertThat(xp("/angels/").getFileName()).isEqualTo(xp("angels")); + assertThat(wp("/angels/").getFileName()).isEqualTo(wp("angels")); + } + + @Test + public void testEquals() { + assertThat(xp("/a/").equals(xp("/a/"))).isTrue(); + assertThat(wp("/a/").equals(wp("/a/"))).isTrue(); + assertThat(xp("/a/").equals(xp("/b/"))).isFalse(); + assertThat(wp("/a/").equals(wp("/b/"))).isFalse(); + assertThat(xp("b").equals(xp("/b"))).isFalse(); + assertThat(wp("b").equals(wp("/b"))).isFalse(); + assertThat(xp("b").equals(xp("b"))).isTrue(); + assertThat(wp("b").equals(wp("b"))).isTrue(); + } + + @Test + public void testEquals_trailingSlash_isConsideredADifferentFile() { + assertThat(xp("/b/").equals(xp("/b"))).isTrue(); + assertThat(wp("/b/").equals(wp("/b"))).isFalse(); // different behavior! + assertThat(xp("/b").equals(xp("/b/"))).isTrue(); + assertThat(wp("/b").equals(wp("/b/"))).isFalse(); // different behavior! + } + + @Test + public void testEquals_redundantComponents_doesNotNormalize() { + assertThat(xp("/.").equals(xp("/"))).isFalse(); + assertThat(wp("/.").equals(wp("/"))).isFalse(); + assertThat(xp("/..").equals(xp("/"))).isFalse(); + assertThat(wp("/..").equals(wp("/"))).isFalse(); + assertThat(xp("a/").equals(xp("a/."))).isFalse(); + assertThat(wp("a/").equals(wp("a/."))).isFalse(); + } + + @Test + public void testSplit() { + assertThat(wp("").split().hasNext()).isFalse(); + assertThat(wp("hi/there").split().hasNext()).isTrue(); + assertThat(wp(wp("hi/there").split().next())).isEqualTo(wp("hi")); + assertThat(ImmutableList.copyOf(Iterators.toArray(wp("hi/there").split(), String.class))) + .containsExactly("hi", "there") + .inOrder(); + } + + @Test + public void testToAbsolute() { + assertThat(wp("lol").toAbsolutePath(wp("/"))).isEqualTo(wp("/lol")); + assertThat(wp("lol/cat").toAbsolutePath(wp("/"))).isEqualTo(wp("/lol/cat")); + } + + @Test + public void testToAbsolute_withCurrentDirectory() { + assertThat(wp("cat").toAbsolutePath(wp("/lol"))).isEqualTo(wp("/lol/cat")); + assertThat(wp("cat").toAbsolutePath(wp("/lol/"))).isEqualTo(wp("/lol/cat")); + assertThat(wp("/hi/there").toAbsolutePath(wp("/lol"))).isEqualTo(wp("/hi/there")); + } + + @Test + public void testToAbsolute_preservesTrailingSlash() { + assertThat(wp("cat/").toAbsolutePath(wp("/lol"))).isEqualTo(wp("/lol/cat/")); + } + + @Test + public void testSubpath() { + assertThat(xp("/eins/zwei/drei/vier").subpath(0, 1)).isEqualTo(xp("eins")); + assertThat(wp("/eins/zwei/drei/vier").subpath(0, 1)).isEqualTo(wp("eins")); + assertThat(xp("/eins/zwei/drei/vier").subpath(0, 2)).isEqualTo(xp("eins/zwei")); + assertThat(wp("/eins/zwei/drei/vier").subpath(0, 2)).isEqualTo(wp("eins/zwei")); + assertThat(xp("eins/zwei/drei/vier/").subpath(1, 4)).isEqualTo(xp("zwei/drei/vier")); + assertThat(wp("eins/zwei/drei/vier/").subpath(1, 4)).isEqualTo(wp("zwei/drei/vier")); + assertThat(xp("eins/zwei/drei/vier/").subpath(2, 4)).isEqualTo(xp("drei/vier")); + assertThat(wp("eins/zwei/drei/vier/").subpath(2, 4)).isEqualTo(wp("drei/vier")); + } + + @Test + public void testSubpath_empty_returnsEmpty() { + assertThat(xp("").subpath(0, 1)).isEqualTo(xp("")); + assertThat(wp("").subpath(0, 1)).isEqualTo(wp("")); + } + + @Test + public void testSubpath_root_throwsIae() { + thrown.expect(IllegalArgumentException.class); + wp("/").subpath(0, 1); + } + + @Test + public void testSubpath_negativeIndex_throwsIae() { + thrown.expect(IllegalArgumentException.class); + wp("/eins/zwei/drei/vier").subpath(-1, 1); + } + + @Test + public void testSubpath_notEnoughElements_throwsIae() { + thrown.expect(IllegalArgumentException.class); + wp("/eins/zwei/drei/vier").subpath(0, 5); + } + + @Test + public void testSubpath_beginAboveEnd_throwsIae() { + thrown.expect(IllegalArgumentException.class); + wp("/eins/zwei/drei/vier").subpath(1, 0); + } + + @Test + public void testSubpath_beginAndEndEqual_throwsIae() { + thrown.expect(IllegalArgumentException.class); + wp("/eins/zwei/drei/vier").subpath(0, 0); + } + + @Test + public void testNameCount() { + assertThat(xp("").getNameCount()).isEqualTo(1); + assertThat(wp("").getNameCount()).isEqualTo(1); + assertThat(xp("/").getNameCount()).isEqualTo(0); + assertThat(wp("/").getNameCount()).isEqualTo(0); + assertThat(xp("/hi/").getNameCount()).isEqualTo(1); + assertThat(wp("/hi/").getNameCount()).isEqualTo(1); + assertThat(xp("/hi/yo").getNameCount()).isEqualTo(2); + assertThat(wp("/hi/yo").getNameCount()).isEqualTo(2); + assertThat(xp("hi/yo").getNameCount()).isEqualTo(2); + assertThat(wp("hi/yo").getNameCount()).isEqualTo(2); + } + + @Test + public void testNameCount_dontPermitEmptyComponents_emptiesGetIgnored() { + assertThat(xp("hi//yo").getNameCount()).isEqualTo(2); + assertThat(wp("hi//yo").getNameCount()).isEqualTo(2); + assertThat(xp("//hi//yo//").getNameCount()).isEqualTo(2); + assertThat(wp("//hi//yo//").getNameCount()).isEqualTo(2); + } + + @Test + public void testGetName() { + assertThat(xp("").getName(0)).isEqualTo(xp("")); + assertThat(wp("").getName(0)).isEqualTo(wp("")); + assertThat(xp("/hi").getName(0)).isEqualTo(xp("hi")); + assertThat(wp("/hi").getName(0)).isEqualTo(wp("hi")); + assertThat(xp("hi/there").getName(1)).isEqualTo(xp("there")); + assertThat(wp("hi/there").getName(1)).isEqualTo(wp("there")); + } + + @Test + public void testGetName_outOfBoundsOnEmpty_throwsIae() { + thrown.expect(IllegalArgumentException.class); + wp("").getName(1); + } + + @Test + public void testGetName_outOfBoundsGreater_throwsIae() { + thrown.expect(IllegalArgumentException.class); + wp("a").getName(1); + } + + @Test + public void testGetName_outOfBoundsLesser_throwsIae() { + thrown.expect(IllegalArgumentException.class); + wp("a").getName(-1); + } + + @Test + public void testGetName_outOfBoundsOnRoot_throwsIae() { + thrown.expect(IllegalArgumentException.class); + wp("/").getName(0); + } + + @Test + public void testCompareTo() { + assertThat(xp("/hi/there").compareTo(xp("/hi/there"))).isEqualTo(0); + assertThat(wp("/hi/there").compareTo(wp("/hi/there"))).isEqualTo(0); + assertThat(xp("/hi/there").compareTo(xp("/hi/therf"))).isEqualTo(-1); + assertThat(wp("/hi/there").compareTo(wp("/hi/therf"))).isEqualTo(-1); + assertThat(xp("/hi/there").compareTo(xp("/hi/therd"))).isEqualTo(1); + assertThat(wp("/hi/there").compareTo(wp("/hi/therd"))).isEqualTo(1); + } + + @Test + public void testCompareTo_emptiesGetIgnored() { + assertThat(xp("a/b").compareTo(xp("a//b"))).isEqualTo(0); + assertThat(wp("a/b").compareTo(wp("a//b"))).isEqualTo(0); + } + + @Test + public void testCompareTo_comparesComponentsIndividually() { + assumeTrue('.' < '/'); + assertThat("hi./there".compareTo("hi/there")).isEqualTo(-1); // demonstration + assertThat("hi.".compareTo("hi")).isEqualTo(1); // demonstration + assertThat(wp("hi./there").compareTo(wp("hi/there"))).isEqualTo(1); + assertThat(wp("hi./there").compareTo(wp("hi/there"))).isEqualTo(1); + assumeTrue('0' > '/'); + assertThat("hi0/there".compareTo("hi/there")).isEqualTo(1); // demonstration + assertThat("hi0".compareTo("hi")).isEqualTo(1); // demonstration + assertThat(wp("hi0/there").compareTo(wp("hi/there"))).isEqualTo(1); + } + + @Test + public void testSorting_shorterPathsFirst() { + assertThat(Ordering.natural().immutableSortedCopy(ImmutableList.of(wp("/a/b"), wp("/a")))) + .containsExactly(wp("/a"), wp("/a/b")) + .inOrder(); + assertThat(Ordering.natural().immutableSortedCopy(ImmutableList.of(wp("/a"), wp("/a/b")))) + .containsExactly(wp("/a"), wp("/a/b")) + .inOrder(); + } + + @Test + public void testSorting_relativePathsFirst() { + assertThat(Ordering.natural().immutableSortedCopy(ImmutableList.of(wp("/a/b"), wp("a")))) + .containsExactly(wp("a"), wp("/a/b")) + .inOrder(); + assertThat(Ordering.natural().immutableSortedCopy(ImmutableList.of(wp("a"), wp("/a/b")))) + .containsExactly(wp("a"), wp("/a/b")) + .inOrder(); + } + + @Test + public void testSorting_trailingSlashLast() { + assertThat(Ordering.natural().immutableSortedCopy(ImmutableList.of(wp("/a/"), wp("/a")))) + .containsExactly(wp("/a"), wp("/a/")) + .inOrder(); + assertThat(Ordering.natural().immutableSortedCopy(ImmutableList.of(wp("/a"), wp("/a/")))) + .containsExactly(wp("/a"), wp("/a/")) + .inOrder(); + } + + @Test + public void testSeemsLikeADirectory() { + assertThat(wp("a").seemsLikeADirectory()).isFalse(); + assertThat(wp("a.").seemsLikeADirectory()).isFalse(); + assertThat(wp("a..").seemsLikeADirectory()).isFalse(); + assertThat(wp("/a").seemsLikeADirectory()).isFalse(); + assertThat(wp("").seemsLikeADirectory()).isTrue(); + assertThat(wp("/").seemsLikeADirectory()).isTrue(); + assertThat(wp(".").seemsLikeADirectory()).isTrue(); + assertThat(wp("/.").seemsLikeADirectory()).isTrue(); + assertThat(wp("..").seemsLikeADirectory()).isTrue(); + assertThat(wp("/..").seemsLikeADirectory()).isTrue(); + } + + @Test + public void testEquals_withEqualsTester() { + new EqualsTester() + .addEqualityGroup(wp("")) + .addEqualityGroup(wp("/"), wp("//")) + .addEqualityGroup(wp("lol")) + .addEqualityGroup(wp("/lol")) + .addEqualityGroup(wp("/lol//"), wp("/lol//")) + .addEqualityGroup(wp("a/b"), wp("a//b")) + .testEquals(); + } + + @Test + public void testNullness() throws Exception { + NullPointerTester tester = new NullPointerTester(); + tester.ignore(Webpath.class.getMethod("equals", Object.class)); + tester.testAllPublicStaticMethods(Webpath.class); + tester.testAllPublicInstanceMethods(wp("solo")); + } +} diff --git a/javatests/io/bazel/rules/closure/webfiles/WebfilesTestSuite.java b/javatests/io/bazel/rules/closure/webfiles/WebfilesTestSuite.java index 5cdb1728f4..f741cc7dc2 100644 --- a/javatests/io/bazel/rules/closure/webfiles/WebfilesTestSuite.java +++ b/javatests/io/bazel/rules/closure/webfiles/WebfilesTestSuite.java @@ -23,6 +23,5 @@ @SuiteClasses({ WebfilesValidatorProgramTest.class, WebfilesValidatorTest.class, - WebpathTest.class, }) class WebfilesTestSuite {} diff --git a/javatests/io/bazel/rules/closure/webfiles/WebpathTest.java b/javatests/io/bazel/rules/closure/webfiles/WebpathTest.java deleted file mode 100644 index 7165a6e696..0000000000 --- a/javatests/io/bazel/rules/closure/webfiles/WebpathTest.java +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2016 The Closure Rules Authors. All Rights Reserved. -// -// Licensed 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 io.bazel.rules.closure.webfiles; - -import static com.google.common.truth.Truth.assertThat; - -import com.google.common.testing.EqualsTester; -import com.google.common.testing.NullPointerTester; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Unit tests for {@link Webpath}. */ -@RunWith(JUnit4.class) -public class WebpathTest { - - @Test - public void imageInSameDirectory_canBeTurnedIntoAbsolutePath() throws Exception { - assertThat(Webpath.get("/foo/bar.html").lookup(Webpath.get("omg.png"))) - .isEqualTo(Webpath.get("/foo/omg.png")); - } - - @Test - public void imageInSubdirectory_canBeTurnedIntoAbsolutePath() throws Exception { - assertThat(Webpath.get("/foo/bar.html").lookup(Webpath.get("a/omg.png"))) - .isEqualTo(Webpath.get("/foo/a/omg.png")); - } - - @Test - public void imageInParentDirectory_removesDots() throws Exception { - assertThat(Webpath.get("/foo/bar.html").lookup(Webpath.get("../omg.png"))) - .isEqualTo(Webpath.get("/omg.png")); - } - - @Test - public void absolutePath_doesNotResolve() throws Exception { - assertThat(Webpath.get("/foo/bar.html").lookup(Webpath.get("/a/b/omg.png"))) - .isEqualTo(Webpath.get("/a/b/omg.png")); - } - - @Test - public void testEquals() throws Exception { - new EqualsTester() - .addEqualityGroup(Webpath.get("a"), Webpath.get("a")) - .addEqualityGroup(Webpath.get("b")) - .addEqualityGroup(Webpath.get("a/b")) - .addEqualityGroup(Webpath.get("/a")) - .testEquals(); - } - - @Test - public void testNulls() throws Exception { - NullPointerTester tester = new NullPointerTester(); - tester.testAllPublicStaticMethods(Webpath.class); - tester.testAllPublicInstanceMethods(Webpath.get("a")); - } -}