blob: f7a84c0470d95b3d5ca3a69dca24ca9e02ba6bad [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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.intellij;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.*;
import java.util.regex.Pattern;
/**
* Encapsulates logic of filtering test classes (classes that contain test-cases).
* <p/>
* We want to have such facility in order to be able to execute different sets of tests like <code>'fast tests'</code>,
* <code>'problem tests'</code> etc.
* <p/>
* I.e. assumed usage scenario is to create object of this class with necessary filtering criteria and use it's
* {@link #matches(String)} method for determining if particular test should be executed.
* <p/>
* The filtering is performed by fully-qualified test class name. There are two ways to define the criteria at the moment:
* <ul>
* <li>
* Define target class name filters (at regexp format) explicitly using
* {@link PatternListTestClassFilter#PatternListTestClassFilter(List) PatternListTestClassFilter};
* </li>
* <li>
* Read class name filters (at regexp format) from the given stream - see {@link #createOn(java.io.Reader, java.util.List)};
* </li>
* </ul>
*/
public class GroupBasedTestClassFilter extends TestClassesFilter {
/**
* Holds reserved test group name that serves as a negation of matching result.
*
* @see #matches(String)
*/
public static final String ALL_EXCLUDE_DEFINED = "ALL_EXCLUDE_DEFINED";
private final Map<String, List<Pattern>> myPatterns = new HashMap<String, List<Pattern>>();
private final List<Pattern> myAllPatterns = new ArrayList<Pattern>();
private final List<Pattern> myTestGroupPatterns;
private boolean myContainsAllExcludeDefinedGroup;
public GroupBasedTestClassFilter(Map<String, List<String>> filters, List<String> testGroupNames) {
//empty group means all patterns from each defined group should be excluded
myContainsAllExcludeDefinedGroup = containsAllExcludeDefinedGroup(testGroupNames);
for (String groupName : filters.keySet()) {
List<String> filterList = filters.get(groupName);
addPatterns(groupName, filterList);
}
myTestGroupPatterns = collectPatternsFor(testGroupNames);
}
private void addPatterns(String groupName, List<String> filterList) {
List<Pattern> patterns = compilePatterns(filterList);
myPatterns.put(groupName, patterns);
myAllPatterns.addAll(patterns);
}
/**
* Creates <code>TestClassesFilter</code> object assuming that the given stream contains grouped test class filters
* at the following format:
* <p/>
* <ul>
* <li>
* every line that starts with <code>'['</code> symbol and ends with <code>']'</code> symbol defines start
* of the new test group. That means that all test class filters that follows this line belongs to the same
* test group which name is defined by the text contained between <code>'['</code> and <code>']'</code>
* </li>
* <li>every line that is not a test-group definition is considered to be a test class filter at regexp format;</li>
* </ul>
* <p/>
* <b>Example</b>
* Consider that given stream points to the following data:
* <pre>
* [CVS]
* com.intellij.cvsSupport2.*
* [STRESS_TESTS]
* com.intellij.application.InspectionPerformanceTest
* com.intellij.application.TraverseUITest
* </pre>
* <p/>
* It defines two test groups:
* <ul>
* <li><b>CVS</b> group with the single test class name pattern <code>'com.intellij.cvsSupport2.*'</code>;</li>
* <li>
* <b>STRESS_TESTS</b> group with the following test class name patterns:
* <ul>
* <li>com.intellij.application.InspectionPerformanceTest</li>
* <li>com.intellij.application.TraverseUITest</li>
* </ul>
* </li>
* </ul>
* <p/>
* This method doesn't suppose itself to be owner of the given stream reader, i.e. it assumes that the stream should be
* closed on caller side.
*
*
* @param reader reader that points to the target test groups config
* @param testGroupNames
* @return newly created {@link GroupBasedTestClassFilter} object with the data contained at the given reader
* @see #matches(String)
*/
@NotNull
public static TestClassesFilter createOn(@NotNull Reader reader, @NotNull List<String> testGroupNames) throws IOException {
return new GroupBasedTestClassFilter(readGroups(reader), testGroupNames);
}
public static Map<String, List<String>> readGroups(Reader reader) throws IOException {
Map<String, List<String>> groupNameToPatternsMap = new HashMap<String, List<String>>();
String currentGroupName = "";
@SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"}) BufferedReader bufferedReader = new BufferedReader(reader);
String line;
while ((line = bufferedReader.readLine()) != null) {
if (line.startsWith("#")) continue;
if (line.startsWith("[") && line.endsWith("]")) {
currentGroupName = line.substring(1, line.length() - 1);
}
else {
if (!groupNameToPatternsMap.containsKey(currentGroupName)) {
groupNameToPatternsMap.put(currentGroupName, new ArrayList<String>());
}
groupNameToPatternsMap.get(currentGroupName).add(line);
}
}
return groupNameToPatternsMap;
}
/**
* Allows to check if given class name belongs to the test group with the given name based on filtering rules encapsulated
* at the current {@link GroupBasedTestClassFilter} object. I.e. this method returns <code>true</code> if given test class name
* is matched with any test class name filter configured for the test group with the given name.
* <p/>
* <b>Note:</b> there is a special case processing when given group name is {@link #ALL_EXCLUDE_DEFINED}. This method
* returns <code>true</code> only if all registered patterns (for all test groups) don't match given test class name.
*
* @param className target test class name to check
* @return <code>true</code> if given test group name is defined (not <code>null</code>) and test class with given
* name belongs to the test group with given name;
* <code>true</code> if given group if undefined or equal to {@link #ALL_EXCLUDE_DEFINED} and given test
* class name is not matched by all registered patterns;
* <code>false</code> otherwise
*/
@Override
public boolean matches(String className) {
if (matchesAnyPattern(myTestGroupPatterns, className)) {
return true;
}
if (myContainsAllExcludeDefinedGroup && !matchesAnyPattern(myAllPatterns, className)) {
return true;
}
return false;
}
private static boolean containsAllExcludeDefinedGroup(List<String> groupNames) {
return groupNames.isEmpty() || groupNames.contains(ALL_EXCLUDE_DEFINED);
}
private List<Pattern> collectPatternsFor(List<String> groupNames) {
List<Pattern> patterns = new ArrayList<Pattern>();
for (String groupName : groupNames) {
if (myPatterns.containsKey(groupName)) {
patterns.addAll(myPatterns.get(groupName));
}
}
return patterns;
}
}