1 package com.intellij.coverage;
3 import com.intellij.openapi.project.Project;
4 import com.intellij.openapi.roots.ProjectFileIndex;
5 import com.intellij.openapi.roots.ProjectRootManager;
6 import com.intellij.openapi.util.Computable;
7 import com.intellij.openapi.util.SystemInfo;
8 import com.intellij.openapi.util.io.FileUtil;
9 import com.intellij.openapi.vfs.VfsUtilCore;
10 import com.intellij.openapi.vfs.VirtualFile;
11 import com.intellij.psi.PsiDirectory;
12 import com.intellij.psi.PsiFile;
13 import com.intellij.rt.coverage.data.ClassData;
14 import com.intellij.rt.coverage.data.LineCoverage;
15 import com.intellij.rt.coverage.data.LineData;
16 import com.intellij.rt.coverage.data.ProjectData;
17 import com.intellij.util.containers.ContainerUtil;
18 import com.intellij.util.containers.HashMap;
19 import org.jetbrains.annotations.NotNull;
20 import org.jetbrains.annotations.Nullable;
23 import java.util.Collections;
30 public abstract class SimpleCoverageAnnotator extends BaseCoverageAnnotator {
32 private final Map<String, FileCoverageInfo> myFileCoverageInfos = new HashMap<String, FileCoverageInfo>();
33 private final Map<String, DirCoverageInfo> myTestDirCoverageInfos = new HashMap<String, DirCoverageInfo>();
34 private final Map<String, DirCoverageInfo> myDirCoverageInfos = new HashMap<String, DirCoverageInfo>();
36 public SimpleCoverageAnnotator(Project project) {
41 public void onSuiteChosen(CoverageSuitesBundle newSuite) {
42 super.onSuiteChosen(newSuite);
44 myFileCoverageInfos.clear();
45 myTestDirCoverageInfos.clear();
46 myDirCoverageInfos.clear();
50 protected DirCoverageInfo getDirCoverageInfo(@NotNull final PsiDirectory directory,
51 @NotNull final CoverageSuitesBundle currentSuite) {
52 final VirtualFile dir = directory.getVirtualFile();
54 final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(directory.getProject()).getFileIndex();
55 //final Module module = projectFileIndex.getModuleForFile(dir);
57 final boolean isInTestContent = projectFileIndex.isInTestSourceContent(dir);
58 if (!currentSuite.isTrackTestFolders() && isInTestContent) {
62 final String path = normalizeFilePath(dir.getPath());
64 return isInTestContent ? myTestDirCoverageInfos.get(path) : myDirCoverageInfos.get(path);
68 public String getDirCoverageInformationString(@NotNull final PsiDirectory directory,
69 @NotNull final CoverageSuitesBundle currentSuite,
70 @NotNull final CoverageDataManager manager) {
71 DirCoverageInfo coverageInfo = getDirCoverageInfo(directory, currentSuite);
72 if (coverageInfo == null) {
76 if (manager.isSubCoverageActive()) {
77 return coverageInfo.coveredLineCount > 0 ? "covered" : null;
80 final String filesCoverageInfo = getFilesCoverageInformationString(coverageInfo);
81 if (filesCoverageInfo != null) {
82 final StringBuilder builder = new StringBuilder();
83 builder.append(filesCoverageInfo);
84 final String linesCoverageInfo = getLinesCoverageInformationString(coverageInfo);
85 if (linesCoverageInfo != null) {
86 builder.append(", ").append(linesCoverageInfo);
88 return builder.toString();
93 // SimpleCoverageAnnotator doesn't require normalized file paths any more
94 // so now coverage report should work w/o usage of this method
96 public static String getFilePath(final String filePath) {
97 return normalizeFilePath(filePath);
100 private static @NotNull String normalizeFilePath(@NotNull String filePath) {
101 if (SystemInfo.isWindows) {
102 filePath = filePath.toLowerCase();
104 return FileUtil.toSystemIndependentName(filePath);
108 public String getFileCoverageInformationString(@NotNull final PsiFile psiFile,
109 @NotNull final CoverageSuitesBundle currentSuite,
110 @NotNull final CoverageDataManager manager) {
111 final VirtualFile file = psiFile.getVirtualFile();
113 final String path = normalizeFilePath(file.getPath());
115 final FileCoverageInfo coverageInfo = myFileCoverageInfos.get(path);
116 if (coverageInfo == null) {
120 if (manager.isSubCoverageActive()) {
121 return coverageInfo.coveredLineCount > 0 ? "covered" : null;
124 return getLinesCoverageInformationString(coverageInfo);
128 protected FileCoverageInfo collectBaseFileCoverage(@NotNull final VirtualFile file,
129 @NotNull final Annotator annotator,
130 @NotNull final ProjectData projectData,
131 @NotNull final Map<String, String> normalizedFiles2Files)
133 final String filePath = normalizeFilePath(file.getPath());
136 final FileCoverageInfo info;
138 final ClassData classData = getClassData(filePath, projectData, normalizedFiles2Files);
139 if (classData != null) {
140 // fill info from coverage data
141 info = fileInfoForCoveredFile(classData);
144 // file wasn't mentioned in coverage information
145 info = fillInfoForUncoveredFile(VfsUtilCore.virtualToIoFile(file));
149 annotator.annotateFile(filePath, info);
154 private static @Nullable ClassData getClassData(
155 final @NotNull String filePath,
156 final @NotNull ProjectData data,
157 final @NotNull Map<String, String> normalizedFiles2Files)
159 final String originalFileName = normalizedFiles2Files.get(filePath);
160 if (originalFileName == null) {
163 return data.getClassData(originalFileName);
167 protected DirCoverageInfo collectFolderCoverage(@NotNull final VirtualFile dir,
168 final @NotNull CoverageDataManager dataManager,
169 final Annotator annotator,
170 final ProjectData projectInfo, boolean trackTestFolders,
171 @NotNull final ProjectFileIndex index,
172 @NotNull final CoverageEngine coverageEngine,
173 Set<VirtualFile> visitedDirs,
174 @NotNull final Map<String, String> normalizedFiles2Files)
176 if (!index.isInContent(dir)) {
180 if (visitedDirs.contains(dir)) {
184 if (!shouldCollectCoverageInsideLibraryDirs()) {
185 if (index.isInLibrarySource(dir) || index.isInLibraryClasses(dir)) {
189 visitedDirs.add(dir);
191 final boolean isInTestSrcContent = index.isInTestSourceContent(dir);
193 // Don't count coverage for tests folders if track test folders is switched off
194 if (!trackTestFolders && isInTestSrcContent) {
198 final VirtualFile[] children = dataManager.doInReadActionIfProjectOpen(new Computable<VirtualFile[]>() {
199 public VirtualFile[] compute() {
200 return dir.getChildren();
204 if (children == null) {
208 final DirCoverageInfo dirCoverageInfo = new DirCoverageInfo();
210 for (VirtualFile fileOrDir : children) {
211 if (fileOrDir.isDirectory()) {
212 final DirCoverageInfo childCoverageInfo =
213 collectFolderCoverage(fileOrDir, dataManager, annotator, projectInfo, trackTestFolders, index,
214 coverageEngine, visitedDirs, normalizedFiles2Files);
216 if (childCoverageInfo != null) {
217 dirCoverageInfo.totalFilesCount += childCoverageInfo.totalFilesCount;
218 dirCoverageInfo.coveredFilesCount += childCoverageInfo.coveredFilesCount;
219 dirCoverageInfo.totalLineCount += childCoverageInfo.totalLineCount;
220 dirCoverageInfo.coveredLineCount += childCoverageInfo.coveredLineCount;
223 else if (coverageEngine.coverageProjectViewStatisticsApplicableTo(fileOrDir)) {
224 // let's count statistics only for ruby-based files
226 final FileCoverageInfo fileInfo =
227 collectBaseFileCoverage(fileOrDir, annotator, projectInfo, normalizedFiles2Files);
229 if (fileInfo != null) {
230 dirCoverageInfo.totalLineCount += fileInfo.totalLineCount;
231 dirCoverageInfo.totalFilesCount++;
233 if (fileInfo.coveredLineCount > 0) {
234 dirCoverageInfo.coveredFilesCount++;
235 dirCoverageInfo.coveredLineCount += fileInfo.coveredLineCount;
242 //TODO - toplevelFilesCoverage - is unused variable!
244 // no sense to include directories without ruby files
245 if (dirCoverageInfo.totalFilesCount == 0) {
249 final String dirPath = normalizeFilePath(dir.getPath());
250 if (isInTestSrcContent) {
251 annotator.annotateTestDirectory(dirPath, dirCoverageInfo);
254 annotator.annotateSourceDirectory(dirPath, dirCoverageInfo);
257 return dirCoverageInfo;
260 protected boolean shouldCollectCoverageInsideLibraryDirs() {
261 // By default returns "true" for backward compatibility
265 public void annotate(@NotNull final VirtualFile contentRoot,
266 @NotNull final CoverageSuitesBundle suite,
267 final @NotNull CoverageDataManager dataManager, @NotNull final ProjectData data,
268 final Project project,
269 final Annotator annotator)
271 if (!contentRoot.isValid()) {
275 // TODO: check name filter!!!!!
277 final ProjectFileIndex index = ProjectRootManager.getInstance(project).getFileIndex();
279 @SuppressWarnings("unchecked") final Set<String> files = data.getClasses().keySet();
280 final Map<String, String> normalizedFiles2Files = ContainerUtil.newHashMap();
281 for (final String file : files) {
282 normalizedFiles2Files.put(normalizeFilePath(file), file);
284 collectFolderCoverage(contentRoot, dataManager, annotator, data,
285 suite.isTrackTestFolders(),
287 suite.getCoverageEngine(),
288 ContainerUtil.<VirtualFile>newHashSet(),
289 Collections.unmodifiableMap(normalizedFiles2Files));
294 protected Runnable createRenewRequest(@NotNull final CoverageSuitesBundle suite, final @NotNull CoverageDataManager dataManager) {
295 final ProjectData data = suite.getCoverageData();
300 return new Runnable() {
302 final Project project = getProject();
304 final ProjectRootManager rootManager = ProjectRootManager.getInstance(project);
306 // find all modules content roots
307 final VirtualFile[] modulesContentRoots = dataManager.doInReadActionIfProjectOpen(new Computable<VirtualFile[]>() {
308 public VirtualFile[] compute() {
309 return rootManager.getContentRoots();
313 if (modulesContentRoots == null) {
317 // gather coverage from all content roots
318 for (VirtualFile root : modulesContentRoots) {
319 annotate(root, suite, dataManager, data, project, new Annotator() {
320 public void annotateSourceDirectory(final String dirPath, final DirCoverageInfo info) {
321 myDirCoverageInfos.put(dirPath, info);
324 public void annotateTestDirectory(final String dirPath, final DirCoverageInfo info) {
325 myTestDirCoverageInfos.put(dirPath, info);
328 public void annotateFile(@NotNull final String filePath, @NotNull final FileCoverageInfo info) {
329 myFileCoverageInfos.put(filePath, info);
334 //final VirtualFile[] roots = ProjectRootManagerEx.getInstanceEx(project).getContentRootsFromAllModules();
335 //index.iterateContentUnderDirectory(roots[0], new ContentIterator() {
336 // public boolean processFile(final VirtualFile fileOrDir) {
337 // // TODO support for libraries and sdk
338 // if (index.isInContent(fileOrDir)) {
339 // final String normalizedPath = RubyCoverageEngine.rcovalizePath(fileOrDir.getPath(), (RubyCoverageSuite)suite);
341 // // TODO - check filters
343 // if (fileOrDir.isDirectory()) {
345 // //if (index.isInTestSourceContent(fileOrDir)) {
346 // // //myTestDirCoverageInfos.put(RubyCoverageEngine.rcovalizePath(fileOrDir.getPath(), (RubyCoverageSuite)suite), )
348 // // myDirCoverageInfos.put(normalizedPath, new FileCoverageInfo());
352 // final ClassData classData = data.getOrCreateClassData(normalizedPath);
353 // if (classData != null) {
354 // final int count = classData.getLines().length;
356 // final FileCoverageInfo info = new FileCoverageInfo();
357 // info.totalLineCount = count;
358 // // let's count covered lines
359 // for (int i = 1; i <= count; i++) {
360 // final LineData lineData = classData.getLineData(i);
361 // if (lineData.getStatus() != LineCoverage.NONE){
362 // info.coveredLineCount++;
365 // myFileCoverageInfos.put(normalizedPath, info);
374 dataManager.triggerPresentationUpdate();
380 protected String getLinesCoverageInformationString(@NotNull final FileCoverageInfo info) {
381 return calcCoveragePercentage(info) + "% lines covered";
384 protected static int calcCoveragePercentage(FileCoverageInfo info) {
385 return calcPercent(info.coveredLineCount, info.totalLineCount);
388 private static int calcPercent(final int covered, final int total) {
389 return total != 0 ? (int)((double)covered / total * 100) : 100;
393 protected String getFilesCoverageInformationString(@NotNull final DirCoverageInfo info) {
394 return calcPercent(info.coveredFilesCount, info.totalFilesCount) + "% files";
398 private static FileCoverageInfo fileInfoForCoveredFile(@NotNull final ClassData classData) {
399 final Object[] lines = classData.getLines();
401 // class data lines = [0, 1, ... count] but first element with index = #0 is fake and isn't
402 // used thus count = length = 1
403 final int count = lines.length - 1;
409 final FileCoverageInfo info = new FileCoverageInfo();
411 int srcLinesCount = 0;
412 int coveredLinesCount = 0;
413 // let's count covered lines
414 for (int i = 1; i <= count; i++) {
415 final LineData lineData = classData.getLineData(i);
416 if (lineData == null) {
417 // Ignore not src code
420 final int status = lineData.getStatus();
421 // covered - if src code & covered (or inferred covered)
422 if (status != LineCoverage.NONE) {
427 info.totalLineCount = srcLinesCount;
428 info.coveredLineCount = coveredLinesCount;
433 protected FileCoverageInfo fillInfoForUncoveredFile(@NotNull File file) {
437 private interface Annotator {
438 void annotateSourceDirectory(final String dirPath, final DirCoverageInfo info);
440 void annotateTestDirectory(final String dirPath, final DirCoverageInfo info);
442 void annotateFile(@NotNull final String filePath, @NotNull final FileCoverageInfo info);