/**
* Copyright 2015 Palantir Technologies, Inc.
*
* 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 com.palantir.giraffe.file.base;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests {@link Globs#toRegex(String, String)}. Test the parser by checking that
* the regex matches inputs correctly instead of checking that the regex equals
* some expected string.
*
* @author bkeyes
*/
public class GlobToRegexTest {
protected static final String SEPARATOR = "/";
@Test
public void basicLiterals() {
assertPattern("console.log", matches("console.log"));
assertPattern("console.log", notMatches("error.log"));
assertPattern("foo/bar/console.log", matches("foo/bar/console.log"));
assertPattern("foo/bar/console.log", notMatches("foo/caz/console.log"));
}
@Test
public void matchesOnFullString() {
assertPattern("console.log", notMatches("foo/bar/console.log"));
assertPattern("foo/bar/console.log", notMatches("console.log"));
}
@Test
public void literalsAreCaseSensitive() {
assertPattern("console.log", notMatches("Console.log"));
}
@Test
public void matchesLiteralRegexMetaChars() {
assertPattern("^(...|abc)+$", matches("^(...|abc)+$"));
assertPattern("^(...|abc)+$", notMatches("123xyzabc"));
assertPattern("^(...|abc)+$", notMatches("abc"));
assertPattern("^(...|abc)+$", notMatches("..."));
}
@Test
public void starMatchesEmptyString() {
assertPattern("*", matches(""));
}
@Test
public void starDoesNotMatchSeparator() {
assertPattern("*", notMatches(SEPARATOR));
}
@Test
public void starMatchesSingleCharacter() {
assertPattern("*", matches("a"));
assertPattern("*", matches("."));
assertPattern("*", matches("*"));
}
@Test
public void starMatchesMultipleChars() {
assertPattern("*", matches("abcd"));
}
@Test
public void singleStarMatchesAny() {
assertPattern("*.java", matches("foo.java"));
assertPattern("*.java", matches("bar.java"));
assertPattern("foo.*", matches("foo.java"));
assertPattern("foo.*", matches("foo.log"));
assertPattern("hello*world", matches("hello world"));
assertPattern("hello*world", matches("helloawesomeworld"));
assertPattern("*.java", notMatches("foo.cpp"));
assertPattern("foo.*", notMatches("bar.log"));
assertPattern("hello*world", notMatches("goodbye world"));
}
@Test
public void multipleStarMatchesAny() {
assertPattern("*test*", matches("footestbar"));
assertPattern("*.*", matches("foo.java"));
assertPattern("*.*", matches(".bashrc"));
assertPattern("foo/*/bar/*/file.log", matches("foo/a/bar/b/file.log"));
assertPattern("foo/*/bar/*/file.log", matches("foo/c/bar/d/file.log"));
assertPattern("*test*", notMatches("foobazbar"));
assertPattern("*.*", notMatches("config"));
assertPattern("foo/*/bar/*/file.log", notMatches("foo/c/baz/d/file.log"));
}
@Test
public void starStarMatchesEmptyString() {
assertPattern("**", matches(""));
}
@Test
public void starStartMatchesSeparator() {
assertPattern("**", matches(SEPARATOR));
}
@Test
public void singleStarStarMatchesAny() {
assertPattern("**.java", matches("foo.java"));
assertPattern("**.java", matches("foo/bar.java"));
assertPattern("foo.**", matches("foo.java"));
assertPattern("foo.**", matches("foo.log/1.zip"));
assertPattern("foo/**/file.log", matches("foo/bar/file.log"));
assertPattern("foo/**/file.log", matches("foo/bar/baz/file.log"));
assertPattern("**.java", notMatches("foo.cpp"));
assertPattern("foo.**", notMatches("bar.log"));
assertPattern("foo/**/file.log", notMatches("foo/bar/baz/file.java"));
}
@Test
public void multipleStarStarMatchesAny() {
assertPattern("**test**", matches("footestbar/baz/biff"));
assertPattern("**.**", matches("foo.java"));
assertPattern("**.**", matches("init.d/apache2"));
assertPattern("foo/**/bar/**/file.log", matches("foo/a/bar/b/c/file.log"));
assertPattern("**test**", notMatches("foobar/baz/biff"));
assertPattern("**.**", notMatches("etc/config"));
assertPattern("foo/**/bar/**/file.log", notMatches("foo/c/baz/d/e/file.log"));
}
@Test
public void questionDoesNotMatchEmptyString() {
assertPattern("?", notMatches(""));
}
@Test
public void questionDoesNotMatchSeparator() {
assertPattern("?", notMatches(SEPARATOR));
}
@Test
public void questionMatchesSingleCharacter() {
assertPattern("?", matches("a"));
assertPattern("?", matches("."));
assertPattern("?", matches("?"));
}
@Test
public void questionDoesNotMatchMultipleCharaters() {
assertPattern("?", notMatches("abcd"));
}
@Test
public void singleQuestionMatchesAny() {
assertPattern("?oo", matches("foo"));
assertPattern("?oo", matches("boo"));
assertPattern("ba?", matches("bar"));
assertPattern("ba?", matches("baz"));
assertPattern("b?z", matches("baz"));
assertPattern("b?z", matches("biz"));
assertPattern("?oo", notMatches("bop"));
assertPattern("?oo", notMatches("igloo"));
assertPattern("ba?", notMatches("biz"));
assertPattern("ba?", notMatches("barb"));
assertPattern("b?z", notMatches("bar"));
assertPattern("b?z", notMatches("beez"));
}
@Test
public void multipleQuestionMatchAny() {
assertPattern("?o?", matches("foo"));
assertPattern("?o?", matches("fog"));
assertPattern("b??r", matches("bear"));
assertPattern("b??r", matches("beer"));
assertPattern("b?z b?z", matches("biz baz"));
assertPattern("?o?", notMatches("fro"));
assertPattern("b??r", notMatches("beef"));
assertPattern("b?z b?z", notMatches("baz bar"));
}
@Test
public void escapedMetaCharacterIsLiteral() {
assertPattern("\\{", matches("{"));
assertPattern("\\*", notMatches("a"));
}
@Test(expected = PatternSyntaxException.class)
public void trailingEscapeThrowsException() {
Globs.toRegex("test\\", SEPARATOR);
}
@Test(expected = PatternSyntaxException.class)
public void escapingNormalCharacterThrowsException() {
Globs.toRegex("\\a", SEPARATOR);
}
@Test
public void escapedCharacterInPattern() {
assertPattern("C:\\\\*", matches("C:\\foo"));
assertPattern("C:\\\\*", notMatches("C:/foo"));
}
@Test
public void simpleBracketExpressionMatchesChars() {
assertPattern("[abc]", matches("a"));
assertPattern("[abc]", matches("b"));
assertPattern("[abc]", matches("c"));
assertPattern("[abc]", notMatches("d"));
}
@Test
public void singleRangeBracketExpressionMatchesChars() {
assertPattern("[a-c]", matches("a"));
assertPattern("[a-c]", matches("b"));
assertPattern("[a-c]", matches("c"));
assertPattern("[a-c]", notMatches("d"));
}
@Test
public void dualRangeBracketExpressionMatchesChars() {
assertPattern("[a-cx-z]", matches("b"));
assertPattern("[a-cx-z]", matches("y"));
assertPattern("[a-cx-z]", matches("a"));
assertPattern("[a-cx-z]", matches("z"));
assertPattern("[a-cx-z]", notMatches("d"));
assertPattern("[a-cx-z]", notMatches("w"));
}
@Test
public void rangeAndLiteralBracketExpressionMatchesChars() {
assertPattern("[A-C248]", matches("B"));
assertPattern("[A-C248]", matches("4"));
assertPattern("[A-C248]", matches("A"));
assertPattern("[A-C248]", matches("8"));
assertPattern("[A-C248]", notMatches("a"));
assertPattern("[A-C248]", notMatches("D"));
assertPattern("[A-C248]", notMatches("3"));
}
@Test
public void negatedSimpleBracketExpressionDoesNotMatchChars() {
assertPattern("[!abc]", notMatches("a"));
assertPattern("[!abc]", notMatches("b"));
assertPattern("[!abc]", notMatches("c"));
assertPattern("[!abc]", matches("1"));
}
@Test
public void negatedRangeBracketExpressionDoesNotMatchChars() {
assertPattern("[!a-c]", notMatches("a"));
assertPattern("[!a-c]", notMatches("b"));
assertPattern("[!a-c]", notMatches("c"));
assertPattern("[!a-c]", matches("1"));
}
@Test
public void bracketExpressionEscapesRegexMetaChars() {
assertPattern("[^a]", matches("a"));
assertPattern("[^a]", matches("^"));
assertPattern("[^a]", notMatches("b"));
}
@Test
public void bracketExpressionMatchesGlobMetaChars() {
assertPattern("[*?\\]", matches("*"));
assertPattern("[*?\\]", matches("?"));
assertPattern("[*?\\]", matches("\\"));
assertPattern("[*?\\]", notMatches("a"));
}
@Test
public void bracketExpressionInPattern() {
assertPattern("*.[cho]", matches("io.c"));
assertPattern("*.[cho]", matches("io.o"));
assertPattern("*.[cho]", matches("io.h"));
assertPattern("*.[cho]", notMatches("io.S"));
}
@Test(expected = PatternSyntaxException.class)
public void emptyBracketExpressionThrowsException() {
Globs.toRegex("[]", SEPARATOR);
}
@Test(expected = PatternSyntaxException.class)
public void unterminatedBracketExpressionThrowsException() {
Globs.toRegex("[abc", SEPARATOR);
}
@Test(expected = PatternSyntaxException.class)
public void unterminatedRangeInBracketExpressionThrowsException() {
Globs.toRegex("[ab-]", SEPARATOR);
}
@Test(expected = PatternSyntaxException.class)
public void reverseRangeInBracketExpressionThrowsException() {
Globs.toRegex("[z-a]", SEPARATOR);
}
@Test(expected = PatternSyntaxException.class)
public void separatorInBracketExpressionThrowsException() {
Globs.toRegex("[ab" + SEPARATOR + "]", SEPARATOR);
}
@Test
public void emptyGroupMatchesEmptyString() {
assertPattern("{}", matches(""));
}
@Test
public void emptyGroupMatchesNoCharacters() {
assertPattern("{}", notMatches("a"));
assertPattern("{}", notMatches("hello"));
}
@Test
public void singlePatternGroupMatchesPattern() {
assertPattern("{a}", matches("a"));
assertPattern("{a}", notMatches("b"));
}
@Test
public void literalsInGroup() {
assertPattern("{java,class}", matches("java"));
assertPattern("{java,class}", matches("class"));
assertPattern("{java,class}", notMatches("html"));
}
@Test
public void starInGroup() {
assertPattern("{*.java,class}", matches("Globs.java"));
assertPattern("{*.java,class}", matches("class"));
assertPattern("{*.java,class}", notMatches("Globs.class"));
}
@Test
public void starStarInGroup() {
assertPattern("{java,**.class}", matches("java"));
assertPattern("{java,**.class}", matches("bin/classes/Globs.class"));
assertPattern("{java,**.class}", notMatches("bin/classes/Globs.java"));
}
@Test
public void questionInGroup() {
assertPattern("{fo?,b?z}", matches("foo"));
assertPattern("{fo?,b?z}", matches("for"));
assertPattern("{fo?,b?z}", matches("baz"));
assertPattern("{fo?,b?z}", matches("biz"));
assertPattern("{fo?,b?z}", notMatches("food"));
assertPattern("{fo?,b?z}", notMatches("beez"));
}
@Test
public void backslashInGroup() {
assertPattern("{\\*.java,class}", matches("*.java"));
assertPattern("{\\*.java,class}", matches("class"));
assertPattern("{\\*.java,class}", notMatches("Globs.java"));
}
@Test
public void bracketExpressionInGroup() {
assertPattern("{ba[rz],#[0-9]}", matches("bar"));
assertPattern("{ba[rz],#[0-9]}", matches("baz"));
assertPattern("{ba[rz],#[0-9]}", matches("#4"));
assertPattern("{ba[rz],#[0-9]}", matches("#0"));
assertPattern("{ba[rz],#[0-9]}", notMatches("#100"));
assertPattern("{ba[rz],#[0-9]}", notMatches("bat"));
}
@Test
public void groupInPattern() {
assertPattern("*.{java,class}", matches("Globs.java"));
assertPattern("*.{java,class}", matches("Globs.class"));
assertPattern("*.{java,class}", notMatches("log.properties"));
}
@Test
public void escapeCommaInGroup() {
assertPattern("{a\\,b,c}", matches("a,b"));
assertPattern("{a\\,b,c}", matches("c"));
assertPattern("{a\\,b,c}", notMatches("a"));
assertPattern("{a\\,b,c}", notMatches("b"));
assertPattern("{a\\,b,c}", notMatches("a\\,b"));
}
@Test
public void escapeBraceInGroup() {
assertPattern("{a\\{b\\},c}", matches("a{b}"));
assertPattern("{a\\{b\\},c}", matches("c"));
assertPattern("{a\\{b\\},c}", notMatches("ab"));
assertPattern("{a\\{b\\},c}", notMatches("a\\{b\\}"));
}
@Test(expected = PatternSyntaxException.class)
public void unterminatedGroupThrowsException() {
Globs.toRegex("{file,dir", SEPARATOR);
}
@Test(expected = PatternSyntaxException.class)
public void nestedGroupThrowsException() {
Globs.toRegex("{foo,{bar,baz}}", SEPARATOR);
}
private static Matcher<String> matches(final String input) {
return new TypeSafeMatcher<String>() {
private String regex;
@Override
public void describeTo(Description description) {
description.appendText("match for ").appendValue(input);
}
@Override
protected boolean matchesSafely(String item) {
regex = Globs.toRegex(item, SEPARATOR);
return Pattern.compile(regex).matcher(input).matches();
}
@Override
protected void describeMismatchSafely(String item, Description mismatch) {
mismatch.appendText("no match with regex ").appendValue(regex)
.appendText(" from glob ").appendValue(item);
}
};
}
private static Matcher<String> notMatches(final String input) {
return new TypeSafeMatcher<String>() {
private String regex;
@Override
public void describeTo(Description description) {
description.appendText("no match for ").appendValue(input);
}
@Override
protected boolean matchesSafely(String item) {
regex = Globs.toRegex(item, SEPARATOR);
return !Pattern.compile(regex).matcher(input).matches();
}
@Override
protected void describeMismatchSafely(String item, Description mismatch) {
mismatch.appendText("match with regex ").appendValue(regex)
.appendText(" from glob ").appendValue(item);
}
};
}
private static void assertPattern(String pattern, Matcher<String> matcher) {
Assert.assertThat("regex is not correct", pattern, matcher);
}
}