< Summary

Information
Class: DirectSight.Parser.CoverageReportParser
Assembly DirectSight
File(s): /home/runner/work/DirectSight/DirectSight/DirectSight/Parser/CoverageReportParser.cs
Line coverage
80%
Covered lines: 110
Uncovered lines: 27
Coverable lines: 137
Total lines: 242
Line coverage: 80.2%
Branch coverage
66%
Covered branches: 20
Total branches: 30
Branch coverage: 66.6%
Method coverage

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%11100%
ParseFiles(...)37.5%8853.84%
CreateConsumer(...)100%22100%
MergeResults(...)100%11100%
CreateProducer(...)66.66%6687.5%
GetXElements()100%88100%
ParseXmlFile()75%4492.3%
GetHumanReadableFileSize(...)0%2246.66%

File(s)

/home/runner/work/DirectSight/DirectSight/DirectSight/Parser/CoverageReportParser.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.IO;
 5using System.Linq;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using System.Xml;
 9using System.Xml.Linq;
 10using DirectSight.Common;
 11using DirectSight.Logging;
 12using DirectSight.Parser.Preprocessing;
 13
 14namespace DirectSight.Parser;
 15
 16/// <summary>
 17/// Initiates the corresponding parser to the given report file.
 18/// </summary>
 19public class CoverageReportParser
 20{
 21    /// <summary>
 22    /// The current merge count.
 23    /// </summary>
 24    private int mergeCount;
 25
 26    /// <summary>
 27    /// Initializes a new instance of the <see cref="CoverageReportParser" /> class.
 28    /// </summary>
 2129    public CoverageReportParser() { }
 30
 31    /// <summary>
 32    /// Tries to initiate the correct parsers for the given reports.
 33    /// </summary>
 34    /// <param name="reportFiles">The report files to parse.</param>
 35    /// <returns>
 36    /// The IParser instance.
 37    /// </returns>
 38    public ParserResult ParseFiles(IReadOnlyCollection<string> reportFiles)
 739    {
 740        ArgumentNullException.ThrowIfNull(reportFiles);
 41
 742        List<Task<ParserResult>> consumers = [];
 43
 44        try
 745        {
 746            using BlockingCollection<ParserResult> blockingCollection = [];
 747            consumers.Add(this.CreateConsumer(blockingCollection));
 48
 749            Task producer = CreateProducer(reportFiles, blockingCollection);
 750            Task.WaitAll([.. consumers, producer]);
 751        }
 052        catch (AggregateException ae)
 053        {
 054            foreach (var e in ae.Flatten().InnerExceptions)
 055            {
 056                if (e is UnsupportedParserException)
 057                {
 058                    throw e;
 59                }
 060            }
 61
 062            throw;
 63        }
 64
 1465        List<ParserResult> results = [.. consumers.Select(t => t.Result)];
 766        ParserResult finalResult = results.First();
 2167        foreach (ParserResult toBeMerged in results.Skip(1))
 068        {
 069            this.MergeResults(finalResult, toBeMerged);
 070        }
 71
 772        return finalResult;
 773    }
 74
 75    /// <summary>
 76    /// Creates the consumer which merges the results.
 77    /// </summary>
 78    /// <param name="collection">The collection to pick results from.</param>
 79    /// <returns>The task containing merged results or null in case this consumer has not merged any result.</returns>
 80    private Task<ParserResult> CreateConsumer(BlockingCollection<ParserResult> collection)
 781    {
 782        return Task.Factory.StartNew(() =>
 783        {
 784            var result = new ParserResult();
 3985            foreach (ParserResult parserResult in collection.GetConsumingEnumerable())
 986            {
 987                this.MergeResults(result, parserResult);
 988            }
 789
 790            return result;
 1491        });
 792    }
 93
 94    /// <summary>
 95    /// Merges the result1 with the result2.
 96    /// </summary>
 97    /// <param name="result1">The first result.</param>
 98    /// <param name="result2">The second result.</param>
 99    private void MergeResults(ParserResult result1, ParserResult result2)
 9100    {
 9101        Interlocked.Increment(ref this.mergeCount);
 9102        int currentProgress = this.mergeCount;
 9103        ConsoleLogger.Debug("Starting merging result {0}", currentProgress);
 9104        result1.Merge(result2);
 9105        ConsoleLogger.Debug("Finished merging result {0}", currentProgress);
 9106    }
 107
 108    /// <summary>
 109    /// Creates the producer which parses the files in parallel and creates parser results out of it.
 110    /// </summary>
 111    /// <param name="reportFiles">The files to parse.</param>
 112    /// <param name="collection">The block collection to add the parsed results to.</param>
 113    /// <returns>The Task.</returns>
 114    private static Task CreateProducer(IReadOnlyCollection<string> reportFiles, BlockingCollection<ParserResult> collect
 7115    {
 7116        return Task.Factory.StartNew(() =>
 7117        {
 7118            try
 7119            {
 7120                int counter = 0;
 7121                Parallel.ForEach(
 7122                reportFiles,
 7123                new ParallelOptions { MaxDegreeOfParallelism = 1 },
 7124                reportFile =>
 7125                {
 7126                    int number = Interlocked.Increment(ref counter);
 7127                    ConsoleLogger.Debug("Loading report '{0}' {1}/{2} in memory", reportFile, number, reportFiles.Count)
 7128                    try
 7129                    {
 7130                        List<ParserResult> parserResults = [.. ParseXmlFile(reportFile)];
 36131                        foreach (ParserResult parserResult in parserResults)
 9132                        {
 9133                            collection.Add(parserResult);
 9134                        }
 7135
 6136                        if (parserResults.Count == 0 && reportFile.EndsWith(".coverage", StringComparison.OrdinalIgnoreC
 0137                        {
 0138                            ConsoleLogger.Warn(
 0139                                "It seems that the report file '{0}' is a binary format generated by a Visual Studio cod
 0140                                + "Please convert to XML format with 'CodeCoverage.exe'",
 0141                                reportFile);
 0142                        }
 7143
 6144                        ConsoleLogger.Debug("Finished parsing '{0}' {1}/{2}", reportFile, number, reportFiles.Count);
 6145                    }
 1146                    catch (Exception ex) when (ex is not UnsupportedParserException)
 1147                    {
 1148                        ConsoleLogger.Error(
 1149                            "Error during reading report '{0}' (Size: {1}): {2}",
 1150                            reportFile,
 1151                            GetHumanReadableFileSize(reportFile),
 1152                            ex.GetExceptionMessageForDisplay());
 1153                    }
 14154                });
 7155            }
 7156            finally
 7157            {
 7158                ConsoleLogger.Debug("Parsing of {0} files completed", reportFiles.Count);
 7159                collection.CompleteAdding();
 7160            }
 14161        });
 7162    }
 163
 164    /// <summary>
 165    /// Load elements in memory balanced manner.
 166    /// </summary>
 167    /// <param name="filePath">The filepath of the covergae file to load.</param>
 168    /// <param name="elementName">The name of the elemens to load.</param>
 169    /// <returns>The elements matchig the name.</returns>
 170    private static IEnumerable<XElement> GetXElements(string filePath, string elementName)
 7171    {
 7172        var readerSettings = new XmlReaderSettings() { DtdProcessing = DtdProcessing.Parse, XmlResolver = null };
 7173        using XmlReader reader = XmlReader.Create(filePath, readerSettings);
 30174        while (reader.Read())
 24175        {
 24176            if (reader.NodeType == XmlNodeType.Element &&
 24177                reader.Name == elementName)
 9178            {
 9179                if (XNode.ReadFrom(reader) is XElement element)
 9180                {
 9181                    yield return element;
 9182                }
 9183            }
 184
 24185        }
 6186    }
 187
 188    /// <summary>
 189    /// Tries to initiate the correct parsers for the given XML report. The result is merged into the given result.
 190    /// The report may contain several reports. For every report an extra parser is initiated.
 191    /// </summary>
 192    /// <param name="filePath">The report file path to parse.</param>
 193    /// <returns>The parser result.</returns>
 194    private static IEnumerable<ParserResult> ParseXmlFile(string filePath)
 7195    {
 7196        var elements = GetXElements(filePath, "CoverageSession").ToArray();
 197
 6198        if (elements.Length > 0)
 6199        {
 36200            foreach (var item in elements)
 9201            {
 9202                ConsoleLogger.Debug("Preprocessing report");
 9203                new OpenCoverReportPreprocessor().Execute(item);
 204
 9205                ConsoleLogger.Debug("Initiating parser for OpenCover");
 9206                yield return new OpenCoverParser().Parse(item);
 9207            }
 208
 6209            yield break;
 210        }
 0211    }
 212
 213    /// <summary>
 214    /// Get the file size in human readable format.
 215    /// If size information is not available (e.g. IOException), '-' is returned.
 216    /// </summary>
 217    /// <param name="fileName">The name of the file.</param>
 218    /// <returns>The file size.</returns>
 219    private static string GetHumanReadableFileSize(string fileName)
 1220    {
 221        try
 1222        {
 1223            long byteCount = new FileInfo(fileName).Length;
 224
 0225            string[] suffixes = ["B", "KB", "MB", "GB"];
 226
 0227            if (byteCount == 0)
 0228            {
 0229                return "0" + suffixes[0];
 230            }
 231
 0232            long bytes = Math.Abs(byteCount);
 0233            int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
 0234            double num = Math.Round(bytes / Math.Pow(1024, place), 1);
 0235            return (Math.Sign(byteCount) * num).ToString() + suffixes[place];
 236        }
 1237        catch (Exception)
 1238        {
 1239            return "-";
 240        }
 1241    }
 242}