blob: ddf01a65e419344415e4670421291765de3a140c [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.flex.tools.codecoverage.reporter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Summary of code coverage at the package, file, and method level.
*
*/
public class CoverageSummary
{
/**
* Used to return the results of splitting the debugfile string.
*/
private static class FilePathComponents
{
public String packageName;
public String filename;
}
private static final TreeSet<String> emptyTreeSet = new TreeSet<String>();
/**
* The coverage data being summarized.
*/
private final CoverageData coverageData;
/**
* Compiled regex patterns to include only specific files.
*/
private final Collection<Pattern> includeFilterPatterns = new ArrayList<Pattern>();
/**
* Compiled regex patterns to exclude only specific files.
*/
private final Collection<Pattern> excludeFilterPatterns = new ArrayList<Pattern>();
/**
* Coverage summary rolled up to the package level.
*
* Key: package name
* Value: PackageSummaryInfo
*/
private final Map<String, PackageSummaryInfo> packageSummaryMap =
new TreeMap<String, PackageSummaryInfo>();
/**
* Coverage summary rolled up to the file level.
*
* key: package;file
* value: SummaryInfo.
*/
private final Map<String, SummaryInfo> fileSummaryMap =
new TreeMap<String, SummaryInfo>();
/**
* Coverage summary for methods.
*
* Key: method name
* Value: SummaryInfo
*/
private final Map<String, SummaryInfo> methodSummaryMap =
new TreeMap<String, SummaryInfo>();
/**
* Summary information for all packages being reported.
*/
private final SummaryInfo overall = new SummaryInfo();
/**
* Association of methods in a file.
*
* Key: package;filename
* Value: Ordered set of method names.
*/
private final Map<String, TreeSet<String>> methodsInFile =
new HashMap<String, TreeSet<String>>();
/**
* Create a coverage summmary object. The coverage summary object
* can process the coverage data and provide coverage data by
* package, file, and method.
*
* @param coverageData the coverage data to generate the summary from.
* @param includeFilters Regex filter patterns. Controls which files
* are included in the coverage summary.
* @param excludeFilters Regex filter patterns. Controls which files
* are excluded in the coverage summary.
*
*/
public CoverageSummary(final CoverageData coverageData,
final Collection<String> includeFilters,
final Collection<String> excludeFilters)
{
this.coverageData = coverageData;
initializePatterns(includeFilters, excludeFilters);
}
/**
* Get the coverage data the summary is generated from.
* @return the coverage data.
*/
public CoverageData getCoverageData()
{
return coverageData;
}
/**
* Get the aggregate summary info of all the included packages.
*
* @return aggregate summary info.
*/
public SummaryInfo getOverallSummaryInfo()
{
return overall;
}
/**
* Entry set of all included packages.
*
* @return Set of packages included in summary data.
*/
public Set<Map.Entry<String, PackageSummaryInfo>> getPackageSummaryEntrySet()
{
return packageSummaryMap.entrySet();
}
/**
* Entry set of all files included in the summary data.
*
* @return Entry set
*/
public Set<Map.Entry<String, SummaryInfo>> getFileSummaryEntrySet()
{
return fileSummaryMap.entrySet();
}
/**
* Get the coverage summmary info for a specific file.
*
* @param packageFileKey key formed by calling createPackageFileKey().
* @return SummaryInfo for the specified file or null the file is
* not included in the summary data.
*/
public SummaryInfo getFileSummaryInfo(final String packageFileKey)
{
return fileSummaryMap.get(packageFileKey);
}
/**
* Get the summary info for a specific method.
*
* @param methodName The name of the method to get the summary info for.
* @return SummyInfo for the specified method or null if the method is
* not included in the summary data.
*/
public SummaryInfo getMethodSummaryInfo(String methodName)
{
return methodSummaryMap.get(methodName);
}
/**
* A set of methods declared in the specified file.
*
* @param filename The name of the AS or MXML file.
* @return Set of method names.
*/
public Set<String> getMethodsInFile(String filename)
{
TreeSet<String> results = methodsInFile.get(filename);
return results != null ? results : emptyTreeSet;
}
/**
* Roll up the coverage data at a method, file, and package level.
* If include filters and/or exclude filters are specified then
* they are applied that this time.
*/
public void processCoverageData()
{
// for all packages;files
for (Map.Entry<String, Map<Integer, LineInfo>> fileEntry : coverageData.entrySet())
{
final String packageFile = fileEntry.getKey();
final FilePathComponents fileComponents = splitPackageFile(packageFile);
final SummaryInfo summaryInfo = new SummaryInfo();
summaryInfo.setName(fileComponents.filename);
// for all line numbers;
// 1. summary at the file level
// 2. record execution status for each method.
for (final Map.Entry<Integer, LineInfo> lineEntry : fileEntry.getValue().entrySet())
{
final LineInfo lineInfo = lineEntry.getValue();
final String methodName = lineInfo.getMethodName();
if (!shouldIncludeFile(fileComponents))
continue;
summaryInfo.setLineExecutionStatus(lineInfo.getLineNumber(),
methodName, lineInfo.isExecuted());
addMethodSummaryInfo(fileComponents.packageName,
fileComponents.filename, methodName, lineInfo);
}
addFileSummaryInfo(fileComponents.packageName,
fileComponents.filename, summaryInfo);
}
}
/**
* Determine if a file should be included in the summary.
* If no include filters or exclude filters are specified then
* all files are included.
*
* @param pathComponents The package and file name to test.
* @return true if the package should be included, false otherwise.
*/
private boolean shouldIncludeFile(final FilePathComponents pathComponents)
{
String className = pathComponents.packageName + "." + pathComponents.filename;
final int index = className.lastIndexOf('.');
assert index != -1 : "no suffix found on filename";
className = className.substring(0, index);
// a class name is included if it is included by the pattern
// and not exclude by the exclude filters.
if (!(includeFilterPatterns.size() == 0) &&
!matches(includeFilterPatterns, className))
{
return false;
}
if (!(excludeFilterPatterns.size() == 0) &&
matches(excludeFilterPatterns, className))
{
return false;
}
return true;
}
/**
* Utility to test is a string matches one of a set of patterns.
*
* @param patterns Collection of patterns
* @param value The value to match
* @return true if the pattern matches, false otherwise.
*/
private boolean matches(final Collection<Pattern> patterns, final String value)
{
for (Pattern pattern : patterns)
{
Matcher matcher = pattern.matcher(value);
if (matcher.matches())
return true;
}
return false;
}
/**
* Create a combination package/file key.
*
* @param packageName The package name part of the key.
* @param filename THe filename part of the key.
* @return Key for the package/file.
*/
public String createPackageFileKey(final String packageName, final String filename)
{
return packageName + ";" + filename;
}
/**
* Split a sourcePath;package/file string. These string come from the
* AS debug_file opcodes and are in the code coverage data files.
*
* @param packageFile The string to split into components.
* @return FilePathComponents
*/
private FilePathComponents splitPackageFile(final String packageFile)
{
final String[] splitResults = packageFile.split(";");
String packageName = splitResults.length > 1 ?
splitResults[splitResults.length - 2] : "";
final String filename = splitResults[splitResults.length - 1];
// convert '\' and '/' path separators to '.' package separators.
if (packageName.length() > 0)
packageName = packageName.replaceAll("[\\\\/]", ".");
final FilePathComponents results = new FilePathComponents();
results.packageName = packageName;
results.filename = filename;
return results;
}
/**
* Aggregate summary information for a file.
*
* This method takes summary information for a file
* and aggregates them at the file, package, and overall coverage.
*
* @param packageName Package name the summary info is for.
* @param filename The file name the summary info is for.
* @param summaryInfo The summary info to aggregate.
*/
private void addFileSummaryInfo(final String packageName, final String filename,
final SummaryInfo summaryInfo)
{
if (summaryInfo.getTotalLineCount() == 0)
return;
// summarize at the package level
fileSummaryMap.put(createPackageFileKey(packageName, filename), summaryInfo);
PackageSummaryInfo packageSummaryInfo = packageSummaryMap.get(packageName);
if (packageSummaryInfo == null)
{
packageSummaryInfo = new PackageSummaryInfo();
packageSummaryInfo.getSummaryInfo().setName(packageName);
packageSummaryMap.put(packageName, packageSummaryInfo);
}
packageSummaryInfo.addFile(packageName, filename);
packageSummaryInfo.add(summaryInfo);
// summary of all of the packages
overall.add(summaryInfo);
}
/**
* Add line coverage information about a method.
*
* This method aggregates the line coverage information for a method.
*
* @param packageName The name of the package the line info is from.
* @param filename The name of the file the line info is from.
* @param methodName The name of the method the line info is from.
* @param lineInfo The line info.
*/
private void addMethodSummaryInfo(final String packageName, final String filename,
final String methodName, final LineInfo lineInfo)
{
// ignore cinit and script init methods.
if (methodName == null || methodName.isEmpty())
return;
// method summary info
SummaryInfo methodSummaryInfo = methodSummaryMap.get(methodName);
if (methodSummaryInfo == null)
{
methodSummaryInfo = new SummaryInfo();
methodSummaryInfo.setName(methodName);
methodSummaryMap.put(methodName, methodSummaryInfo);
final String key = createPackageFileKey(packageName, filename);
TreeSet<String> treeSet = methodsInFile.get(key);
if (treeSet == null)
{
treeSet = new TreeSet<String>();
treeSet.add(methodName);
methodsInFile.put(key, treeSet);
}
treeSet.add(methodName);
}
methodSummaryInfo.setLineExecutionStatus(lineInfo.getLineNumber(),
methodName, lineInfo.isExecuted());
}
/**
* Initialize the patterns for the include and exclude filters.
*
* @param includeFilters regex strings of classes to include.
* @param excludeFilters regex strings of classes to exclude.
*/
private void initializePatterns(final Collection<String> includeFilters,
final Collection<String>excludeFilters)
{
for (final String includeFilter : includeFilters)
{
includeFilterPatterns.add(Pattern.compile(includeFilter));
}
for (final String excludeFilter : excludeFilters)
{
excludeFilterPatterns.add(Pattern.compile(excludeFilter));
}
}
}