blob: 6b62461d9cce5205661f308084fbc00c605428b2 [file] [log] [blame]
/*
* Copyright 2013-present Facebook, 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.facebook.buck.test.selectors;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* A way of matching a test-method in a test-class, and saying whether or not to include any matches
* in a test run.
*
* A {@link TestDescription} will match if this selector's class-part is a substring of the
* {@link TestDescription}'s full class-name, or if this selector's class-name, when interpreted
* as a java.util.regex regular-expression, matches the {@link TestDescription}'s full class-name.
*
* (The same rules apply for the method-name as well. If this selector's class-part or method-part
* are null, all class-names or method-names will match.)
*/
public class TestSelector {
/** Defining @Nullable locally here because we cannot import
* javax.annotation.Nullable;
*according to comment in src/com/facebook/buck/test/selectors/BUCK */
@interface Nullable{}
private final boolean inclusive;
@Nullable private final Pattern classPattern;
@Nullable private final Pattern methodPattern;
TestSelector(
boolean inclusive,
@Nullable Pattern classPattern,
@Nullable Pattern methodPattern) {
this.inclusive = inclusive;
this.classPattern = classPattern;
this.methodPattern = methodPattern;
}
/**
* Build a {@link TestSelector} from the given String. Selector strings should be of the form
* "[is-exclusive][class-part]#[method-part]". If "[is-exclusive]" is a "!" then this selector
* will exclude tests, otherwise it will include tests.
*
* If the class-part (or method-part) are omitted, then all classes or methods will match.
* Consequently "#" means "include everything" and "!#" means "exclude everything".
*
* If the selector string doesn't contain a "#" at all, it is interpreted as a class-part.
*
* @param rawSelectorString An unparsed selector string.
*/
public static TestSelector buildFromSelectorString(String rawSelectorString) {
if (rawSelectorString == null || rawSelectorString.isEmpty()) {
throw new RuntimeException("Cannot build from a null or empty string!");
}
boolean isInclusive = true;
String remainder;
if (rawSelectorString.charAt(0) == '!') {
isInclusive = false;
remainder = rawSelectorString.substring(1);
} else {
remainder = rawSelectorString;
}
Pattern classPattern = null;
Pattern methodPattern = null;
String[] parts = remainder.split("#", -1);
try {
switch (parts.length) {
// "com.example.Test", "com.example.Test#"
case 1:
classPattern = getPatternOrNull(parts[0]);
break;
// "com.example.Test#testX", "#testX", "#"
case 2:
classPattern = getPatternOrNull(parts[0]);
methodPattern = getPatternOrNull(parts[1]);
break;
// Invalid string, like "##"
default:
throw new TestSelectorParseException(String.format(
"Test selector '%s' contains more than one '#'!",
rawSelectorString));
}
} catch (PatternSyntaxException e) {
throw new TestSelectorParseException(String.format(
"Regular expression error in '%s': %s",
rawSelectorString,
e.getMessage()));
}
return new TestSelector(isInclusive, classPattern, methodPattern);
}
String getRawSelector() {
StringBuilder builder = new StringBuilder();
if (!inclusive) {
builder.append('!');
}
if (classPattern != null) {
builder.append(classPattern.toString());
}
builder.append('#');
if (methodPattern != null) {
builder.append(methodPattern.toString());
}
return builder.toString();
}
@Nullable private static Pattern getPatternOrNull(String string) {
if (string.isEmpty()) {
return null;
} else {
if (!string.endsWith("$")) {
string = string + "$";
}
return Pattern.compile(string);
}
}
String getExplanation() {
return String.format("%s class:%s method:%s",
isInclusive() ? "include" : "exclude",
isMatchAnyClass() ? "<any>" : classPattern,
isMatchAnyMethod() ? "<any>" : methodPattern);
}
boolean isInclusive() {
return inclusive;
}
boolean isMatchAnyClass() {
return classPattern == null;
}
boolean isMatchAnyMethod() {
return methodPattern == null;
}
boolean matches(TestDescription description) {
boolean isClassMatch;
boolean isMethodMatch;
if (classPattern == null) {
isClassMatch = true;
} else {
isClassMatch = classPattern.matcher(description.getClassName()).find();
}
if (methodPattern == null) {
isMethodMatch = true;
} else {
isMethodMatch = methodPattern.matcher(description.getMethodName()).find();
}
return isClassMatch && isMethodMatch;
}
}