/* * 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 {@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. * * <p>(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 PatternTestSelector implements TestSelector { static final PatternTestSelector INCLUDE_EVERYTHING = new PatternTestSelector(true, null, null); static final PatternTestSelector EXCLUDE_EVERYTHING = new PatternTestSelector(false, null, null); private final boolean inclusive; @Nullable private final Pattern classPattern; @Nullable private final Pattern methodPattern; private PatternTestSelector( boolean inclusive, @Nullable Pattern classPattern, @Nullable Pattern methodPattern) { this.inclusive = inclusive; this.classPattern = classPattern; this.methodPattern = methodPattern; } /** * Build a {@link PatternTestSelector} 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. * * <p>If the class-part (or method-part) are omitted, then all classes or methods will match. * Consequently "#" means "include everything" and "!#" means "exclude everything". * * <p>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; } // Reuse univeral inclusion if (remainder.equals("#")) { return isInclusive ? INCLUDE_EVERYTHING : EXCLUDE_EVERYTHING; } Pattern classPattern; 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 PatternTestSelector(isInclusive, classPattern, methodPattern); } @Override public 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); } } @Override public String getExplanation() { if (isMatchAnyClass() && isMatchAnyMethod()) { return isInclusive() ? "include everything else" : "exclude everything else"; } return String.format( "%s class:%s method:%s", isInclusive() ? "include" : "exclude", isMatchAnyClass() ? "<any>" : classPattern, isMatchAnyMethod() ? "<any>" : methodPattern); } @Override public boolean isInclusive() { return inclusive; } @Override public boolean isMatchAnyClass() { return classPattern == null; } @Override public boolean isMatchAnyMethod() { return methodPattern == null; } @Override public boolean matches(TestDescription description) { return matchesClassName(description.getClassName()) && matchesMethodName(description.getMethodName()); } @Override public boolean matchesClassName(String className) { if (classPattern == null) { return true; } return classPattern.matcher(className).find(); } private boolean matchesMethodName(String methodName) { if (methodPattern == null) { return true; } return methodPattern.matcher(methodName).find(); } }