public static void processGatheredCoverage(RunConfigurationBase configuration) { final Project project = configuration.getProject(); if (project.isDisposed()) return; final CoverageDataManager coverageDataManager = CoverageDataManager.getInstance(project); final CoverageEnabledConfiguration coverageEnabledConfiguration = CoverageEnabledConfiguration.getOrCreate(configuration); //noinspection ConstantConditions final CoverageSuite coverageSuite = coverageEnabledConfiguration.getCurrentCoverageSuite(); if (coverageSuite != null) { ((BaseCoverageSuite) coverageSuite).setConfiguration(configuration); coverageDataManager.coverageGathered(coverageSuite); } }
@Nullable public String getDirCoverageInformationString( @NotNull final PsiDirectory directory, @NotNull final CoverageSuitesBundle currentSuite, @NotNull final CoverageDataManager manager) { DirCoverageInfo coverageInfo = getDirCoverageInfo(directory, currentSuite); if (coverageInfo == null) { return null; } if (manager.isSubCoverageActive()) { return coverageInfo.coveredLineCount > 0 ? "covered" : null; } final String filesCoverageInfo = getFilesCoverageInformationString(coverageInfo); if (filesCoverageInfo != null) { final StringBuilder builder = new StringBuilder(); builder.append(filesCoverageInfo); final String linesCoverageInfo = getLinesCoverageInformationString(coverageInfo); if (linesCoverageInfo != null) { builder.append(", ").append(linesCoverageInfo); } return builder.toString(); } return null; }
private void collectNonCoveredFileInfo( final File outputFile, final List<RangeHighlighter> highlighters, final MarkupModel markupModel, final TreeMap<Integer, LineData> executableLines, final boolean coverageByTestApplicable) { final CoverageSuitesBundle coverageSuite = CoverageDataManager.getInstance(myProject).getCurrentSuitesBundle(); if (coverageSuite == null) return; final TIntIntHashMap mapping; if (outputFile.lastModified() < getVirtualFile().getTimeStamp()) { mapping = getOldToNewLineMapping(outputFile.lastModified()); if (mapping == null) return; } else { mapping = null; } final List<Integer> uncoveredLines = coverageSuite .getCoverageEngine() .collectSrcLinesForUntouchedFile(outputFile, coverageSuite); final int lineCount = myDocument.getLineCount(); if (uncoveredLines == null) { for (int lineNumber = 0; lineNumber < lineCount; lineNumber++) { addHighlighter( outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable, coverageSuite, lineNumber, lineNumber); } } else { for (int lineNumber : uncoveredLines) { if (lineNumber >= lineCount) { continue; } final int updatedLineNumber = mapping != null ? mapping.get(lineNumber) : lineNumber; addHighlighter( outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable, coverageSuite, lineNumber, updatedLineNumber); } } }
@Nullable public String getFileCoverageInformationString( @NotNull final PsiFile psiFile, @NotNull final CoverageSuitesBundle currentSuite, @NotNull final CoverageDataManager manager) { final VirtualFile file = psiFile.getVirtualFile(); assert file != null; final String path = normalizeFilePath(file.getPath()); final FileCoverageInfo coverageInfo = myFileCoverageInfos.get(path); if (coverageInfo == null) { return null; } if (manager.isSubCoverageActive()) { return coverageInfo.coveredLineCount > 0 ? "covered" : null; } return getLinesCoverageInformationString(coverageInfo); }
@Override @Nullable protected Runnable createRenewRequest( @NotNull final CoverageSuitesBundle suite, final @NotNull CoverageDataManager dataManager) { final ProjectData data = suite.getCoverageData(); if (data == null) { return null; } return () -> { final Project project = getProject(); final ProjectRootManager rootManager = ProjectRootManager.getInstance(project); // find all modules content roots final VirtualFile[] modulesContentRoots = dataManager.doInReadActionIfProjectOpen(() -> rootManager.getContentRoots()); if (modulesContentRoots == null) { return; } // gather coverage from all content roots for (VirtualFile root : modulesContentRoots) { annotate( root, suite, dataManager, data, project, new Annotator() { public void annotateSourceDirectory( final String dirPath, final DirCoverageInfo info) { myDirCoverageInfos.put(dirPath, info); } public void annotateTestDirectory(final String dirPath, final DirCoverageInfo info) { myTestDirCoverageInfos.put(dirPath, info); } public void annotateFile( @NotNull final String filePath, @NotNull final FileCoverageInfo info) { myFileCoverageInfos.put(filePath, info); } }); } // final VirtualFile[] roots = // ProjectRootManagerEx.getInstanceEx(project).getContentRootsFromAllModules(); // index.iterateContentUnderDirectory(roots[0], new ContentIterator() { // public boolean processFile(final VirtualFile fileOrDir) { // // TODO support for libraries and sdk // if (index.isInContent(fileOrDir)) { // final String normalizedPath = RubyCoverageEngine.rcovalizePath(fileOrDir.getPath(), // (RubyCoverageSuite)suite); // // // TODO - check filters // // if (fileOrDir.isDirectory()) { // //// process dir // //if (index.isInTestSourceContent(fileOrDir)) { // // // //myTestDirCoverageInfos.put(RubyCoverageEngine.rcovalizePath(fileOrDir.getPath(), // (RubyCoverageSuite)suite), ) // //} else { // // myDirCoverageInfos.put(normalizedPath, new FileCoverageInfo()); // //} // } else { // // process file // final ClassData classData = data.getOrCreateClassData(normalizedPath); // if (classData != null) { // final int count = classData.getLines().length; // if (count != 0) { // final FileCoverageInfo info = new FileCoverageInfo(); // info.totalLineCount = count; // // let's count covered lines // for (int i = 1; i <= count; i++) { // final LineData lineData = classData.getLineData(i); // if (lineData.getStatus() != LineCoverage.NONE){ // info.coveredLineCount++; // } // } // myFileCoverageInfos.put(normalizedPath, info); // } // } // } // } // return true; // } // }); dataManager.triggerPresentationUpdate(); }; }
@Nullable protected DirCoverageInfo collectFolderCoverage( @NotNull final VirtualFile dir, final @NotNull CoverageDataManager dataManager, final Annotator annotator, final ProjectData projectInfo, boolean trackTestFolders, @NotNull final ProjectFileIndex index, @NotNull final CoverageEngine coverageEngine, Set<VirtualFile> visitedDirs, @NotNull final Map<String, String> normalizedFiles2Files) { if (!index.isInContent(dir)) { return null; } if (visitedDirs.contains(dir)) { return null; } if (!shouldCollectCoverageInsideLibraryDirs()) { if (index.isInLibrarySource(dir) || index.isInLibraryClasses(dir)) { return null; } } visitedDirs.add(dir); final boolean isInTestSrcContent = TestSourcesFilter.isTestSources(dir, getProject()); // Don't count coverage for tests folders if track test folders is switched off if (!trackTestFolders && isInTestSrcContent) { return null; } final VirtualFile[] children = dataManager.doInReadActionIfProjectOpen(dir::getChildren); if (children == null) { return null; } final DirCoverageInfo dirCoverageInfo = new DirCoverageInfo(); for (VirtualFile fileOrDir : children) { if (fileOrDir.isDirectory()) { final DirCoverageInfo childCoverageInfo = collectFolderCoverage( fileOrDir, dataManager, annotator, projectInfo, trackTestFolders, index, coverageEngine, visitedDirs, normalizedFiles2Files); if (childCoverageInfo != null) { dirCoverageInfo.totalFilesCount += childCoverageInfo.totalFilesCount; dirCoverageInfo.coveredFilesCount += childCoverageInfo.coveredFilesCount; dirCoverageInfo.totalLineCount += childCoverageInfo.totalLineCount; dirCoverageInfo.coveredLineCount += childCoverageInfo.coveredLineCount; } } else if (coverageEngine.coverageProjectViewStatisticsApplicableTo(fileOrDir)) { // let's count statistics only for ruby-based files final FileCoverageInfo fileInfo = collectBaseFileCoverage(fileOrDir, annotator, projectInfo, normalizedFiles2Files); if (fileInfo != null) { dirCoverageInfo.totalLineCount += fileInfo.totalLineCount; dirCoverageInfo.totalFilesCount++; if (fileInfo.coveredLineCount > 0) { dirCoverageInfo.coveredFilesCount++; dirCoverageInfo.coveredLineCount += fileInfo.coveredLineCount; } } } } // TODO - toplevelFilesCoverage - is unused variable! // no sense to include directories without ruby files if (dirCoverageInfo.totalFilesCount == 0) { return null; } final String dirPath = normalizeFilePath(dir.getPath()); if (isInTestSrcContent) { annotator.annotateTestDirectory(dirPath, dirCoverageInfo); } else { annotator.annotateSourceDirectory(dirPath, dirCoverageInfo); } return dirCoverageInfo; }
private void coverageDataNotFound(final CoverageSuitesBundle suite) { showEditorWarningMessage(CodeInsightBundle.message("coverage.data.not.found")); for (CoverageSuite coverageSuite : suite.getSuites()) { CoverageDataManager.getInstance(myProject).removeCoverageSuite(coverageSuite); } }
private RangeHighlighter createRangeHighlighter( final long date, final MarkupModel markupModel, final boolean coverageByTestApplicable, final TreeMap<Integer, LineData> executableLines, @Nullable final String className, final int line, final int lineNumberInCurrent, @NotNull final CoverageSuitesBundle coverageSuite, Object[] lines) { EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); final TextAttributes attributes = scheme.getAttributes(CoverageLineMarkerRenderer.getAttributesKey(line, executableLines)); TextAttributes textAttributes = null; if (attributes.getBackgroundColor() != null) { textAttributes = attributes; } final int startOffset = myDocument.getLineStartOffset(lineNumberInCurrent); final int endOffset = myDocument.getLineEndOffset(lineNumberInCurrent); final RangeHighlighter highlighter = markupModel.addRangeHighlighter( startOffset, endOffset, HighlighterLayer.SELECTION - 1, textAttributes, HighlighterTargetArea.LINES_IN_RANGE); final Function<Integer, Integer> newToOldConverter = newLine -> { if (myEditor == null) return -1; final TIntIntHashMap oldLineMapping = getNewToOldLineMapping(date); return oldLineMapping != null ? oldLineMapping.get(newLine.intValue()) : newLine.intValue(); }; final Function<Integer, Integer> oldToNewConverter = newLine -> { if (myEditor == null) return -1; final TIntIntHashMap newLineMapping = getOldToNewLineMapping(date); return newLineMapping != null ? newLineMapping.get(newLine.intValue()) : newLine.intValue(); }; final CoverageLineMarkerRenderer markerRenderer = coverageSuite .getCoverageEngine() .getLineMarkerRenderer( line, className, executableLines, coverageByTestApplicable, coverageSuite, newToOldConverter, oldToNewConverter, CoverageDataManager.getInstance(myProject).isSubCoverageActive()); highlighter.setLineMarkerRenderer(markerRenderer); final LineData lineData = className != null ? (LineData) lines[line + 1] : null; if (lineData != null && lineData.getStatus() == LineCoverage.NONE) { highlighter.setErrorStripeMarkColor(markerRenderer.getErrorStripeColor(myEditor)); highlighter.setThinErrorStripeMark(true); highlighter.setGreedyToLeft(true); highlighter.setGreedyToRight(true); } return highlighter; }
public void showCoverageInformation(final CoverageSuitesBundle suite) { // Store the values of myFile and myEditor in local variables to avoid an NPE after dispose() // has been called in the EDT. final PsiFile psiFile = myFile; final Editor editor = myEditor; final Document document = myDocument; if (editor == null || psiFile == null || document == null) return; final MarkupModel markupModel = DocumentMarkupModel.forDocument(document, myProject, true); final List<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>(); final ProjectData data = suite.getCoverageData(); if (data == null) { coverageDataNotFound(suite); return; } final CoverageEngine engine = suite.getCoverageEngine(); final Set<String> qualifiedNames = engine.getQualifiedNames(psiFile); // let's find old content in local history and build mapping from old lines to new one // local history doesn't index libraries, so let's distinguish libraries content with other one final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); final VirtualFile file = psiFile.getVirtualFile(); LOG.assertTrue(file != null); final long fileTimeStamp = file.getTimeStamp(); final long coverageTimeStamp = suite.getLastCoverageTimeStamp(); final TIntIntHashMap oldToNewLineMapping; // do not show coverage info over cls if (engine.isInLibraryClasses(myProject, file)) { return; } // if in libraries content if (projectFileIndex.isInLibrarySource(file)) { // compare file and coverage timestamps if (fileTimeStamp > coverageTimeStamp) { showEditorWarningMessage(CodeInsightBundle.message("coverage.data.outdated")); return; } oldToNewLineMapping = null; } else { // check local history oldToNewLineMapping = getOldToNewLineMapping(coverageTimeStamp); if (oldToNewLineMapping == null) { // if history for file isn't available let's check timestamps if (fileTimeStamp > coverageTimeStamp && classesArePresentInCoverageData(data, qualifiedNames)) { showEditorWarningMessage(CodeInsightBundle.message("coverage.data.outdated")); return; } } } if (editor.getUserData(COVERAGE_HIGHLIGHTERS) != null) { // highlighters already collected - no need to do it twice return; } final Module module = ApplicationManager.getApplication() .runReadAction( new Computable<Module>() { @Nullable @Override public Module compute() { return ModuleUtilCore.findModuleForPsiElement(psiFile); } }); if (module != null) { if (engine.recompileProjectAndRerunAction( module, suite, () -> CoverageDataManager.getInstance(myProject).chooseSuitesBundle(suite))) { return; } } // now if oldToNewLineMapping is null we should use f(x)=id(x) mapping // E.g. all *.class files for java source file with several classes final Set<File> outputFiles = engine.getCorrespondingOutputFiles(psiFile, module, suite); final boolean subCoverageActive = CoverageDataManager.getInstance(myProject).isSubCoverageActive(); final boolean coverageByTestApplicable = suite.isCoverageByTestApplicable() && !(subCoverageActive && suite.isCoverageByTestEnabled()); final TreeMap<Integer, LineData> executableLines = new TreeMap<Integer, LineData>(); final TreeMap<Integer, Object[]> classLines = new TreeMap<Integer, Object[]>(); final TreeMap<Integer, String> classNames = new TreeMap<Integer, String>(); class HighlightersCollector { private void collect(File outputFile, final String qualifiedName) { final ClassData fileData = data.getClassData(qualifiedName); if (fileData != null) { final Object[] lines = fileData.getLines(); if (lines != null) { final Object[] postProcessedLines = suite.getCoverageEngine().postProcessExecutableLines(lines, editor); for (Object lineData : postProcessedLines) { if (lineData instanceof LineData) { final int line = ((LineData) lineData).getLineNumber() - 1; final int lineNumberInCurrent; if (oldToNewLineMapping != null) { // use mapping based on local history if (!oldToNewLineMapping.contains(line)) { continue; } lineNumberInCurrent = oldToNewLineMapping.get(line); } else { // use id mapping lineNumberInCurrent = line; } LOG.assertTrue(lineNumberInCurrent < document.getLineCount()); executableLines.put(line, (LineData) lineData); classLines.put(line, postProcessedLines); classNames.put(line, qualifiedName); ApplicationManager.getApplication() .invokeLater( () -> { if (lineNumberInCurrent >= document.getLineCount()) return; final RangeHighlighter highlighter = createRangeHighlighter( suite.getLastCoverageTimeStamp(), markupModel, coverageByTestApplicable, executableLines, qualifiedName, line, lineNumberInCurrent, suite, postProcessedLines); highlighters.add(highlighter); }); } } } } else if (outputFile != null && !subCoverageActive && engine.includeUntouchedFileInCoverage(qualifiedName, outputFile, psiFile, suite)) { collectNonCoveredFileInfo( outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable); } } } final HighlightersCollector collector = new HighlightersCollector(); if (!outputFiles.isEmpty()) { for (File outputFile : outputFiles) { final String qualifiedName = engine.getQualifiedName(outputFile, psiFile); if (qualifiedName != null) { collector.collect(outputFile, qualifiedName); } } } else { // check non-compilable classes which present in ProjectData for (String qName : qualifiedNames) { collector.collect(null, qName); } } ApplicationManager.getApplication() .invokeLater( () -> { if (myEditor != null && highlighters.size() > 0) { editor.putUserData(COVERAGE_HIGHLIGHTERS, highlighters); } }); final DocumentListener documentListener = new DocumentAdapter() { @Override public void documentChanged(final DocumentEvent e) { myNewToOldLines = null; myOldToNewLines = null; List<RangeHighlighter> rangeHighlighters = editor.getUserData(COVERAGE_HIGHLIGHTERS); if (rangeHighlighters == null) rangeHighlighters = new ArrayList<RangeHighlighter>(); int offset = e.getOffset(); final int lineNumber = document.getLineNumber(offset); final int lastLineNumber = document.getLineNumber(offset + e.getNewLength()); final TextRange changeRange = new TextRange( document.getLineStartOffset(lineNumber), document.getLineEndOffset(lastLineNumber)); for (Iterator<RangeHighlighter> it = rangeHighlighters.iterator(); it.hasNext(); ) { final RangeHighlighter highlighter = it.next(); if (!highlighter.isValid() || TextRange.create(highlighter).intersects(changeRange)) { highlighter.dispose(); it.remove(); } } final List<RangeHighlighter> highlighters = rangeHighlighters; myUpdateAlarm.cancelAllRequests(); if (!myUpdateAlarm.isDisposed()) { myUpdateAlarm.addRequest( () -> { final TIntIntHashMap newToOldLineMapping = getNewToOldLineMapping(suite.getLastCoverageTimeStamp()); if (newToOldLineMapping != null) { ApplicationManager.getApplication() .invokeLater( () -> { if (editor.isDisposed()) return; for (int line = lineNumber; line <= lastLineNumber; line++) { final int oldLineNumber = newToOldLineMapping.get(line); final LineData lineData = executableLines.get(oldLineNumber); if (lineData != null) { RangeHighlighter rangeHighlighter = createRangeHighlighter( suite.getLastCoverageTimeStamp(), markupModel, coverageByTestApplicable, executableLines, classNames.get(oldLineNumber), oldLineNumber, line, suite, classLines.get(oldLineNumber)); highlighters.add(rangeHighlighter); } } editor.putUserData( COVERAGE_HIGHLIGHTERS, highlighters.size() > 0 ? highlighters : null); }); } }, 100); } } }; document.addDocumentListener(documentListener); editor.putUserData(COVERAGE_DOCUMENT_LISTENER, documentListener); }