From e81f3db147645debddd703906ba83f09b696a991 Mon Sep 17 00:00:00 2001 From: "Anna.Kozlova" Date: Fri, 11 May 2018 20:04:11 +0200 Subject: [PATCH] coverage: sync root walk down through src & output cleanup --- .../intellij/coverage/PackageAnnotator.java | 348 +++++++++--------- .../testData/annotator/src1/p/Main1.java | 7 + .../CoverageAnnotatorIntegrationTest.java | 46 +++ 3 files changed, 227 insertions(+), 174 deletions(-) create mode 100644 plugins/coverage/testData/annotator/src1/p/Main1.java diff --git a/plugins/coverage/src/com/intellij/coverage/PackageAnnotator.java b/plugins/coverage/src/com/intellij/coverage/PackageAnnotator.java index 6a4a6f77411c..4bf5809f747c 100644 --- a/plugins/coverage/src/com/intellij/coverage/PackageAnnotator.java +++ b/plugins/coverage/src/com/intellij/coverage/PackageAnnotator.java @@ -72,11 +72,11 @@ public class PackageAnnotator { default void annotateTestDirectory(VirtualFile virtualFile, PackageCoverageInfo packageCoverageInfo, Module module) {} - default void annotatePackage(String packageQualifiedName, PackageCoverageInfo packageCoverageInfo){} - + default void annotatePackage(String packageQualifiedName, PackageCoverageInfo packageCoverageInfo) {} + default void annotatePackage(String packageQualifiedName, PackageCoverageInfo packageCoverageInfo, boolean flatten) {} - default void annotateClass(String classQualifiedName, ClassCoverageInfo classCoverageInfo){} + default void annotateClass(String classQualifiedName, ClassCoverageInfo classCoverageInfo) {} } public static abstract class SummaryCoverageInfo { @@ -165,22 +165,27 @@ public class PackageAnnotator { final String rootPackageVMName = qualifiedName.replaceAll("\\.", "/"); final VirtualFile[] productionRoots = myCoverageManager.doInReadActionIfProjectOpen( () -> OrderEnumerator.orderEntries(module) - .withoutSdk() - .withoutLibraries() - .withoutDepModules() - .productionOnly() - .classes() - .getRoots()); + .withoutSdk() + .withoutLibraries() + .withoutDepModules() + .productionOnly() + .classes() + .getRoots()); final Set productionRootsSet = new SmartHashSet<>(); if (productionRoots != null) { + Map dirsMap = new HashMap<>(); + RootWalker rootWalker = new RootWalker(packageCoverageMap, dirsMap, flattenPackageCoverageMap, annotator, data, suite, + GlobalSearchScope.moduleScope(module)); for (VirtualFile output : productionRoots) { productionRootsSet.add(output); File outputRoot = findRelativeFile(rootPackageVMName, output); if (outputRoot.exists()) { - collectCoverageInformation(outputRoot, packageCoverageMap, flattenPackageCoverageMap, data, rootPackageVMName, annotator, - module, - suite, false); + rootWalker.collectCoverageDataInRoot(outputRoot, rootPackageVMName, prepareRoots(module, rootPackageVMName, false)); + } + + for (DirCoverageInfo dir : dirsMap.values()) { + annotator.annotateSourceDirectory(dir.sourceRoot, dir, module); } } } @@ -188,22 +193,26 @@ public class PackageAnnotator { if (suite.isTrackTestFolders()) { final VirtualFile[] allRoots = myCoverageManager.doInReadActionIfProjectOpen( () -> OrderEnumerator.orderEntries(module) - .withoutSdk() - .withoutLibraries() - .withoutDepModules() - .classes() - .getRoots()); + .withoutSdk() + .withoutLibraries() + .withoutDepModules() + .classes() + .getRoots()); if (allRoots != null) { + Map dirsMap = new HashMap<>(); + RootWalker rootWalker = new RootWalker(packageCoverageMap, dirsMap, flattenPackageCoverageMap, annotator, data, suite, + GlobalSearchScope.moduleScope(module)); for (VirtualFile root : allRoots) { if (productionRootsSet.contains(root)) continue; final File outputRoot = findRelativeFile(rootPackageVMName, root); if (outputRoot.exists()) { - collectCoverageInformation(outputRoot, packageCoverageMap, flattenPackageCoverageMap, data, rootPackageVMName, annotator, - module, - suite, true); + rootWalker.collectCoverageDataInRoot(outputRoot, rootPackageVMName, prepareRoots(module, rootPackageVMName, true)); } } + for (DirCoverageInfo dir : dirsMap.values()) { + annotator.annotateTestDirectory(dir.sourceRoot, dir, module); + } } } } @@ -221,6 +230,22 @@ public class PackageAnnotator { } } + private static VirtualFile[] prepareRoots(Module module, String rootPackageVMName, boolean isTestHierarchy) { + List result = new ArrayList<>(); + final ContentEntry[] contentEntries = ModuleRootManager.getInstance(module).getContentEntries(); + for (ContentEntry contentEntry : contentEntries) { + for (SourceFolder folder : contentEntry + .getSourceFolders(isTestHierarchy ? JavaSourceRootType.TEST_SOURCE : JavaSourceRootType.SOURCE)) { + final VirtualFile file = folder.getFile(); + if (file == null) continue; + final String prefix = folder.getPackagePrefix().replaceAll("\\.", "/"); + final VirtualFile relativeSrcRoot = file.findFileByRelativePath(StringUtil.trimStart(rootPackageVMName, prefix)); + result.add(relativeSrcRoot); + } + } + return result.toArray(VirtualFile.EMPTY_ARRAY); + } + private static File findRelativeFile(String rootPackageVMName, VirtualFile output) { File outputRoot = VfsUtilCore.virtualToIoFile(output); outputRoot = rootPackageVMName.length() > 0 ? new File(outputRoot, FileUtil.toSystemDependentName(rootPackageVMName)) : outputRoot; @@ -264,173 +289,138 @@ public class PackageAnnotator { } } } - - @Nullable - private DirCoverageInfo[] collectCoverageInformation(final File packageOutputRoot, - final Map packageCoverageMap, - Map flattenPackageCoverageMap, - final ProjectData projectInfo, - final String packageVMName, - final Annotator annotator, - final Module module, - final CoverageSuitesBundle bundle, - final boolean isTestHierarchy) { - final List dirs = new ArrayList<>(); - final ContentEntry[] contentEntries = ModuleRootManager.getInstance(module).getContentEntries(); - for (ContentEntry contentEntry : contentEntries) { - for (SourceFolder folder : contentEntry.getSourceFolders(isTestHierarchy ? JavaSourceRootType.TEST_SOURCE : JavaSourceRootType.SOURCE)) { - final VirtualFile file = folder.getFile(); - if (file == null) continue; - final String prefix = folder.getPackagePrefix().replaceAll("\\.", "/"); - final VirtualFile relativeSrcRoot = file.findFileByRelativePath(StringUtil.trimStart(packageVMName, prefix)); - dirs.add(new DirCoverageInfo(relativeSrcRoot)); - } + + private class RootWalker { + private final Map packageCoverageMap; + private final Map dirsCoverageMap; + private final Map flattenPackageCoverageMap; + private final Annotator annotator; + private final ProjectData projectInfo; + private final CoverageSuitesBundle bundle; + private final GlobalSearchScope globalSearchScope; + + private RootWalker(Map packageCoverageMap, + Map dirsCoverageMap, + Map flattenPackageCoverageMap, + Annotator annotator, + ProjectData projectInfo, + CoverageSuitesBundle bundle, + GlobalSearchScope globalSearchScope) { + this.packageCoverageMap = packageCoverageMap; + this.dirsCoverageMap = dirsCoverageMap; + this.flattenPackageCoverageMap = flattenPackageCoverageMap; + this.annotator = annotator; + this.projectInfo = projectInfo; + this.bundle = bundle; + this.globalSearchScope = globalSearchScope; } - final PackageCoverageInfo classWithoutSourceCoverageInfo = new PackageCoverageInfo(); - final File[] children = packageOutputRoot.listFiles(); + private void collectCoverageDataInRoot(final File packageOutputRoot, + final String packageVMName, + final VirtualFile[] sourceRoots) { + final File[] children = packageOutputRoot.listFiles(); + if (children == null) return; - if (children == null) return null; + final PackageCoverageInfo classWithoutSourceCoverageInfo = new PackageCoverageInfo(); - Map toplevelClassCoverage = new HashMap<>(); - for (File child : children) { - if (child.isDirectory()) { - final String childName = child.getName(); - final String childPackageVMName = packageVMName.length() > 0 ? packageVMName + "/" + childName : childName; - final DirCoverageInfo[] childCoverageInfo = - collectCoverageInformation(child, packageCoverageMap, flattenPackageCoverageMap, projectInfo, childPackageVMName, annotator, module, - bundle, isTestHierarchy); - if (childCoverageInfo != null) { - for (int i = 0; i < childCoverageInfo.length; i++) { - DirCoverageInfo coverageInfo = childCoverageInfo[i]; - final DirCoverageInfo parentDir = dirs.get(i); - parentDir.totalClassCount += coverageInfo.totalClassCount; - parentDir.coveredClassCount += coverageInfo.coveredClassCount; - parentDir.totalLineCount += coverageInfo.totalLineCount; - parentDir.coveredLineCount += coverageInfo.coveredLineCount; - parentDir.totalMethodCount += coverageInfo.totalMethodCount; - parentDir.coveredMethodCount += coverageInfo.coveredMethodCount; - parentDir.totalBranchCount += coverageInfo.totalBranchCount; - parentDir.coveredBranchCount += coverageInfo.coveredBranchCount; + Map toplevelClassCoverage = new HashMap<>(); + for (File child : children) { + if (child.isDirectory()) { + final String childName = child.getName(); + final String childPackageVMName = packageVMName.length() > 0 ? packageVMName + "/" + childName : childName; + VirtualFile[] childSourceRoots = Arrays.stream(sourceRoots) + .map(root -> root.findFileByRelativePath(childName)) + .toArray(VirtualFile[]::new); + collectCoverageDataInRoot(child, childPackageVMName, childSourceRoots); + for (int i = 0; i < sourceRoots.length; i++) { + VirtualFile childRoot = childSourceRoots[i]; + PackageCoverageInfo coverageInfo = childRoot != null ? dirsCoverageMap.get(childRoot) : null; + if (coverageInfo != null) { + dirsCoverageMap.computeIfAbsent(sourceRoots[i], srcRoot -> new DirCoverageInfo(srcRoot)) + .append(coverageInfo); + } + } + } + else { + if (isClassFile(child)) { + final String childName = getClassName(child); + final String classFqVMName = packageVMName.length() > 0 ? packageVMName + "/" + childName : childName; + final String toplevelClassSrcFQName = getSourceToplevelFQName(classFqVMName); + + if (ignoreClass(bundle, child, toplevelClassSrcFQName)) continue; + + final Ref containingFileRef = new Ref<>(); + final Ref psiClassRef = new Ref<>(); + final Boolean isInSource = DumbService.getInstance(myProject).runReadActionInSmartMode(() -> { + if (myProject.isDisposed()) return null; + final PsiClass aClass = + JavaPsiFacade.getInstance(myProject).findClass(toplevelClassSrcFQName, globalSearchScope); + if (aClass == null || !aClass.isValid()) return Boolean.FALSE; + psiClassRef.set(aClass); + PsiElement element = aClass.getNavigationElement(); + containingFileRef.set(PsiUtilCore.getVirtualFile(element)); + if (containingFileRef.isNull()) { + LOG.info("No virtual file found for: " + aClass); + return null; + } + return bundle.getCoverageEngine().acceptedByFilters(element.getContainingFile(), bundle); + }); + + if (isInSource != null && isInSource.booleanValue()) { + VirtualFile virtualFile = containingFileRef.get(); + PackageCoverageInfo coverageInfoForClass; + if (virtualFile != null) { + coverageInfoForClass = dirsCoverageMap.computeIfAbsent(virtualFile.getParent(), DirCoverageInfo::new); + } + else if (Arrays.stream(JavaCoverageEngineExtension.EP_NAME.getExtensions()) + .anyMatch(extension -> extension.keepCoverageInfoForClassWithoutSource(bundle, child))) { + coverageInfoForClass = classWithoutSourceCoverageInfo; + } + else { + continue; + } + collectClassCoverageInformation(child, psiClassRef.get(), coverageInfoForClass, projectInfo, toplevelClassCoverage, + classFqVMName.replace("/", "."), toplevelClassSrcFQName); + } } } } - else { - if (isClassFile(child)) { - final String childName = getClassName(child); - final String classFqVMName = packageVMName.length() > 0 ? packageVMName + "/" + childName : childName; - final String toplevelClassSrcFQName = getSourceToplevelFQName(classFqVMName); - final Ref containingFileRef = new Ref<>(); - final Ref psiClassRef = new Ref<>(); - final Boolean isInSource = DumbService.getInstance(myProject).runReadActionInSmartMode(() -> { - if (myProject.isDisposed()) return null; - final PsiClass aClass = - JavaPsiFacade.getInstance(myManager.getProject()).findClass(toplevelClassSrcFQName, GlobalSearchScope.moduleScope(module)); - if (aClass == null || !aClass.isValid()) return Boolean.FALSE; - psiClassRef.set(aClass); - PsiElement element = aClass.getNavigationElement(); - containingFileRef.set(PsiUtilCore.getVirtualFile(element)); - if (containingFileRef.isNull()) { - LOG.info("No virtual file found for: " + aClass); - return null; - } - return bundle.getCoverageEngine().acceptedByFilters(element.getContainingFile(), bundle); - }); - PackageCoverageInfo coverageInfoForClass = null; - String classCoverageKey = classFqVMName.replace('/', '.'); - boolean ignoreClass = false; - boolean keepWithoutSource = false; - for (JavaCoverageEngineExtension extension : JavaCoverageEngineExtension.EP_NAME.getExtensions()) { - if (extension.ignoreCoverageForClass(bundle, child)) { - ignoreClass = true; - break; - } - if (extension.keepCoverageInfoForClassWithoutSource(bundle, child)) { - keepWithoutSource = true; - } - } - if (!ignoreClass) { - for (CoverageSuite suite : bundle.getSuites()) { - if (suite instanceof JavaCoverageSuite && - ((JavaCoverageSuite)suite).isClassFiltered(classCoverageKey, ((JavaCoverageSuite)suite).getExcludedClassNames())) { - ignoreClass = true; - break; - } - } - } - if (ignoreClass) { - continue; - } - if (isInSource != null && isInSource.booleanValue()) { - for (DirCoverageInfo dirCoverageInfo : dirs) { - if (dirCoverageInfo.sourceRoot != null && VfsUtilCore.isAncestor(dirCoverageInfo.sourceRoot, containingFileRef.get(), false)) { - coverageInfoForClass = dirCoverageInfo; - classCoverageKey = toplevelClassSrcFQName; - break; - } - } - } - if (coverageInfoForClass == null && keepWithoutSource) { - coverageInfoForClass = classWithoutSourceCoverageInfo; - } - if (coverageInfoForClass != null) { - collectClassCoverageInformation(child, psiClassRef.get(), coverageInfoForClass, projectInfo, toplevelClassCoverage, - classFqVMName.replace("/", "."), classCoverageKey); - } + PackageCoverageInfo flattenPackageCoverageInfo = flattenPackageCoverageMap.computeIfAbsent(packageVMName, k -> new PackageCoverageInfo()); + for (Map.Entry entry : toplevelClassCoverage.entrySet()) { + final ClassCoverageInfo coverageInfo = entry.getValue(); + flattenPackageCoverageInfo.append(coverageInfo); + annotator.annotateClass(entry.getKey(), coverageInfo); + } + + PackageCoverageInfo packageCoverageInfo = packageCoverageMap.computeIfAbsent(packageVMName, k -> new PackageCoverageInfo()); + Arrays.stream(sourceRoots) + .map(dirsCoverageMap::get) + .filter(Objects::nonNull) + .forEach(packageCoverageInfo::append); + packageCoverageInfo.append(classWithoutSourceCoverageInfo); + } + + private boolean ignoreClass(CoverageSuitesBundle bundle, File child, String toplevelClassSrcFQName) { + for (JavaCoverageEngineExtension extension : JavaCoverageEngineExtension.EP_NAME.getExtensions()) { + if (extension.ignoreCoverageForClass(bundle, child)) { + return true; } } - } - - for (Map.Entry entry : toplevelClassCoverage.entrySet()) { - final String toplevelClassName = entry.getKey(); - final ClassCoverageInfo coverageInfo = entry.getValue(); - annotator.annotateClass(toplevelClassName, coverageInfo); - } - - PackageCoverageInfo flattenPackageCoverageInfo = getOrCreateCoverageInfo(flattenPackageCoverageMap, packageVMName); - for (Map.Entry entry : toplevelClassCoverage.entrySet()) { - final ClassCoverageInfo coverageInfo = entry.getValue(); - flattenPackageCoverageInfo.append(coverageInfo); - } - - PackageCoverageInfo packageCoverageInfo = getOrCreateCoverageInfo(packageCoverageMap, packageVMName); - for (DirCoverageInfo dir : dirs) { - packageCoverageInfo.append(dir); - - if (isTestHierarchy) { - annotator.annotateTestDirectory(dir.sourceRoot, dir, module); - } - else { - annotator.annotateSourceDirectory(dir.sourceRoot, dir, module); + for (CoverageSuite suite : bundle.getSuites()) { + if (suite instanceof JavaCoverageSuite && + ((JavaCoverageSuite)suite).isClassFiltered(toplevelClassSrcFQName, ((JavaCoverageSuite)suite).getExcludedClassNames())) { + return true; + } } + return false; } - packageCoverageInfo.append(classWithoutSourceCoverageInfo); - - return dirs.toArray(new DirCoverageInfo[0]); - } - - private static boolean isClassFile(File classFile) { - return classFile.getName().endsWith(".class"); - } - - private static String getClassName(File classFile) { - return StringUtil.trimEnd(classFile.getName(), ".class"); - } - - private static PackageCoverageInfo getOrCreateCoverageInfo(final Map packageCoverageMap, - final String packageVMName) { - PackageCoverageInfo coverageInfo = packageCoverageMap.get(packageVMName); - if (coverageInfo == null) { - coverageInfo = new PackageCoverageInfo(); - packageCoverageMap.put(packageVMName, coverageInfo); - } - return coverageInfo; } private void collectClassCoverageInformation(final File classFile, - @Nullable final PsiClass psiClass, final PackageCoverageInfo packageCoverageInfo, - final ProjectData projectInfo, + @Nullable final PsiClass psiClass, + final PackageCoverageInfo packageCoverageInfo, + ProjectData projectInfo, final Map toplevelClassCoverage, final String className, final String toplevelClassSrcFQName) { @@ -449,8 +439,9 @@ public class PackageAnnotator { else if (lineData.getStatus() == LineCoverage.PARTIAL) { toplevelClassCoverageInfo.partiallyCoveredLineCount++; } - else if ((myIgnoreEmptyPrivateConstructors || myIgnoreImplicitConstructor) && - isGeneratedDefaultConstructor(psiClass, lineData.getMethodSignature(), myIgnoreImplicitConstructor, myIgnoreEmptyPrivateConstructors)) { + else if ((myIgnoreEmptyPrivateConstructors || myIgnoreImplicitConstructor) && + isGeneratedDefaultConstructor(psiClass, lineData.getMethodSignature(), myIgnoreImplicitConstructor, + myIgnoreEmptyPrivateConstructors)) { continue; } toplevelClassCoverageInfo.totalLineCount++; @@ -521,6 +512,15 @@ public class PackageAnnotator { } } + private static boolean isClassFile(File classFile) { + return classFile.getName().endsWith(".class"); + } + + private static String getClassName(File classFile) { + return StringUtil.trimEnd(classFile.getName(), ".class"); + } + + /** * Checks if the method is a default constructor generated by the compiler. Such constructors are not marked as synthetic * in the bytecode, so we need to look at the PSI to see if the class defines such a constructor. diff --git a/plugins/coverage/testData/annotator/src1/p/Main1.java b/plugins/coverage/testData/annotator/src1/p/Main1.java new file mode 100644 index 000000000000..9e2a1842af52 --- /dev/null +++ b/plugins/coverage/testData/annotator/src1/p/Main1.java @@ -0,0 +1,7 @@ +package p; + +public class Main1 { + public static void main(String[] args) { + System.out.println(""); + } +} \ No newline at end of file diff --git a/plugins/coverage/testSrc/com/intellij/coverage/CoverageAnnotatorIntegrationTest.java b/plugins/coverage/testSrc/com/intellij/coverage/CoverageAnnotatorIntegrationTest.java index 70b15eab0ea2..84e11a1250ed 100644 --- a/plugins/coverage/testSrc/com/intellij/coverage/CoverageAnnotatorIntegrationTest.java +++ b/plugins/coverage/testSrc/com/intellij/coverage/CoverageAnnotatorIntegrationTest.java @@ -4,12 +4,15 @@ package com.intellij.coverage; import com.intellij.openapi.application.PluginPathManager; import com.intellij.openapi.compiler.CompilerMessage; import com.intellij.openapi.compiler.CompilerMessageCategory; +import com.intellij.openapi.module.Module; import com.intellij.openapi.roots.ContentEntry; import com.intellij.openapi.roots.ModuleRootModificationUtil; import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiFile; import com.intellij.rt.coverage.data.ClassData; +import com.intellij.rt.coverage.data.LineData; import com.intellij.rt.coverage.data.ProjectData; import com.intellij.testFramework.CompilerTester; import com.intellij.testFramework.ModuleTestCase; @@ -17,7 +20,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.Assert; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; public class CoverageAnnotatorIntegrationTest extends ModuleTestCase { @@ -45,6 +50,7 @@ public class CoverageAnnotatorIntegrationTest extends ModuleTestCase { model -> { ContentEntry contentEntry = model.addContentEntry(getTestContentRoot()); contentEntry.addSourceFolder(getTestContentRoot() + "/src", false); + contentEntry.addSourceFolder(getTestContentRoot() + "/src1", false); contentEntry.addSourceFolder(getTestContentRoot() + "/test", true); }); } @@ -77,6 +83,46 @@ public class CoverageAnnotatorIntegrationTest extends ModuleTestCase { }); } + public void testMultipleSourceRoots() { + CoverageSuitesBundle suite = new CoverageSuitesBundle(new JavaCoverageSuite(new JavaCoverageEngine())) { + @Nullable + @Override + public ProjectData getCoverageData() { + return new ProjectData() { + @Override + public ClassData getClassData(String name) { + ClassData data = new ClassData(name); + LineData lineData = new LineData(1, "foo"); + lineData.setHits(1); + data.registerMethodSignature(lineData); + LineData lineData1 = new LineData(2, "bar"); + data.registerMethodSignature(lineData1); + data.setLines(new LineData[] {lineData, lineData1}); + return data; + } + }; + } + }; + PackageAnnotator annotator = new PackageAnnotator(JavaPsiFacade.getInstance(getProject()).findPackage("p")); + Map dirs = new HashMap<>(); + annotator.annotate(suite, new PackageAnnotator.Annotator() { + @Override + public void annotateSourceDirectory(VirtualFile virtualFile, + PackageAnnotator.PackageCoverageInfo packageCoverageInfo, + Module module) { + dirs.put(virtualFile, packageCoverageInfo); + } + }); + + assertEquals(2, dirs.size()); + for (PackageAnnotator.PackageCoverageInfo coverageInfo : dirs.values()) { + assertTrue(coverageInfo instanceof PackageAnnotator.DirCoverageInfo); + assertEquals(1, coverageInfo.coveredClassCount); + assertEquals(1, coverageInfo.coveredLineCount); + assertEquals(1, coverageInfo.coveredMethodCount); + } + } + @Override protected void tearDown() throws Exception { try {