Skip to content

Commit

Permalink
#457: Added support for altcover embedded files
Browse files Browse the repository at this point in the history
  • Loading branch information
danielpalme committed Nov 5, 2021
1 parent 607fa8f commit 540cfdf
Show file tree
Hide file tree
Showing 12 changed files with 327 additions and 17 deletions.
5 changes: 5 additions & 0 deletions src/Readme.txt
Expand Up @@ -64,6 +64,11 @@ For further details take a look at LICENSE.txt.

CHANGELOG

4.9.0.0

* New: #397: Added .NET 6 support
* New: #457: Added support for altcover embedded files

4.8.14.0

* Fix: #459: Improved support for C++/CLI for OpenCover coverage files
Expand Down
Expand Up @@ -28,7 +28,7 @@ public void GetFiles_EmptyDirectory_NoFilesFound()
public void GetFiles_SingleDirectory_XmlFilesFound()
{
var files = GlobbingFileSearch.GetFiles(Path.Combine(FileManager.GetCSharpReportDirectory(), "*.xml")).ToArray();
Assert.Equal(20, files.Length);
Assert.Equal(21, files.Length);
}

[Fact]
Expand Down
Expand Up @@ -58,7 +58,7 @@ public void GetFiles_EmptyDirectory_NoFilesFound()
public void GetFiles_SingleDirectory_XmlFilesFound()
{
var files = WildCardFileSearch.GetFiles(Path.Combine(FileManager.GetCSharpReportDirectory(), "*.xml")).ToArray();
Assert.Equal(20, files.Length);
Assert.Equal(21, files.Length);
}

[Fact]
Expand Down
58 changes: 58 additions & 0 deletions src/ReportGenerator.Core.Test/Parser/Analysis/CodeFileTest.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Moq;
using Palmmedia.ReportGenerator.Core.Parser.Analysis;
using Palmmedia.ReportGenerator.Core.Parser.FileReading;
using Xunit;
Expand Down Expand Up @@ -260,6 +261,63 @@ public void AnalyzeFile_NonExistingFile_AnalysisIsReturned()
Assert.Empty(fileAnalysis.Lines);
}

/// <summary>
/// A test for AnalyzeFile
/// </summary>
[Fact]
public void AnalyzeFile_AdditionFileReaderNoError_RegularFileReaderIgnored()
{
var additionalFileReaderMock = new Mock<IFileReader>();
string error = null;
additionalFileReaderMock.Setup(f => f.LoadFile(It.IsAny<string>(), out error))
.Returns(new[] { "Test" });

var fileReaderMock = new Mock<IFileReader>();

var sut = new CodeFile("C:\\temp\\Other.cs", new int[] { -2, -1, 0, 1 }, new LineVisitStatus[] { LineVisitStatus.NotCoverable, LineVisitStatus.NotCoverable, LineVisitStatus.NotCovered, LineVisitStatus.Covered }, additionalFileReaderMock.Object);

Assert.Null(sut.TotalLines);

var fileAnalysis = sut.AnalyzeFile(fileReaderMock.Object);

Assert.NotNull(fileAnalysis);
Assert.Null(fileAnalysis.Error);

additionalFileReaderMock.Verify(f => f.LoadFile(It.IsAny<string>(), out error), Times.Once);
fileReaderMock.Verify(f => f.LoadFile(It.IsAny<string>(), out error), Times.Never);
}

/// <summary>
/// A test for AnalyzeFile
/// </summary>
[Fact]
public void AnalyzeFile_AdditionFileReaderReturnsError_RegularFileReaderUsed()
{
var additionalFileReaderMock = new Mock<IFileReader>();
string error = "Some error";
additionalFileReaderMock.Setup(f => f.LoadFile(It.IsAny<string>(), out error))
.Returns((string[])null);

var fileReaderMock = new Mock<IFileReader>();
fileReaderMock.Setup(f => f.LoadFile(It.IsAny<string>(), out error))
.Returns(new[] { "Test" });

var sut = new CodeFile("C:\\temp\\Other.cs", new int[] { -2, -1, 0, 1 }, new LineVisitStatus[] { LineVisitStatus.NotCoverable, LineVisitStatus.NotCoverable, LineVisitStatus.NotCovered, LineVisitStatus.Covered }, additionalFileReaderMock.Object);

Assert.Null(sut.TotalLines);

var fileAnalysis = sut.AnalyzeFile(fileReaderMock.Object);

Assert.NotNull(fileAnalysis);
Assert.NotNull(fileAnalysis.Error);
Assert.Equal(fileAnalysis.Path, fileAnalysis.Path);
Assert.Null(sut.TotalLines);
Assert.Empty(fileAnalysis.Lines);

additionalFileReaderMock.Verify(f => f.LoadFile(It.IsAny<string>(), out error), Times.Once);
fileReaderMock.Verify(f => f.LoadFile(It.IsAny<string>(), out error), Times.Once);
}

/// <summary>
/// A test for Equals
/// </summary>
Expand Down
@@ -0,0 +1,32 @@
using Palmmedia.ReportGenerator.Core.Parser.FileReading;
using Xunit;

namespace Palmmedia.ReportGenerator.Core.Test.Parser.FileReading
{
/// <summary>
/// This is a test class for AltCoverEmbeddedFileReader and is intended
/// to contain all AltCoverEmbeddedFileReader Unit Tests
/// </summary>
public class AltCoverEmbeddedFileReaderTest
{
[Fact]
public void ValidEncodedFile_CorrectContentAndNoError()
{
var sut = new AltCoverEmbeddedFileReader("hY7BCoJQEEX3Qf8wS4XoAxIDd21qk62ixWiDCqOJM4+Q6Mta9En9Qs+eGRTS3TyYd++593G7Tydgtd+2olTOI9WmSIzSTjAj7/saY5ORyjwSoTLhdgYR8+m8NqxFzRRqY8g/OGRRKTUVMggh0xFSRhFYG1FMcxqQC/gucfGLezrVJuEiBbH/VQYbLAkuYHcEcB0zxVTWjPrfuELJx00/a70+VtkRszdD+7LhkFuo/4F1CpfgbV6heHB33T6E4DnaB/PKBw5gRz0B");

string[] lines = sut.LoadFile("DoesNotMatter", out string error);
Assert.Null(error);
Assert.True(lines.Length > 0);
}

[Fact]
public void InvalidEncodedFile_InvalidContent_Error()
{
var sut = new AltCoverEmbeddedFileReader("xyz");

string[] lines = sut.LoadFile("DoesNotMatter", out string error);
Assert.NotNull(error);
Assert.Null(lines);
}
}
}
Expand Up @@ -5,8 +5,8 @@
namespace Palmmedia.ReportGenerator.Core.Test.Parser.FileReading
{
/// <summary>
/// This is a test class for CloverParser and is intended
/// to contain all CloverParser Unit Tests
/// This is a test class for LocalFileReader and is intended
/// to contain all LocalFileReader Unit Tests
/// </summary>
[Collection("FileManager")]
public class LocalFileReaderTest
Expand Down
57 changes: 55 additions & 2 deletions src/ReportGenerator.Core/Parser/Analysis/CodeFile.cs
Expand Up @@ -50,14 +50,31 @@ public class CodeFile
/// </summary>
private IDictionary<int, ICollection<Branch>> branches;

/// <summary>
/// The optional additional file reader.
/// </summary>
private IFileReader additionalFileReader;

/// <summary>
/// Initializes a new instance of the <see cref="CodeFile" /> class.
/// </summary>
/// <param name="path">The path of the file.</param>
/// <param name="lineCoverage">The line coverage.</param>
/// <param name="lineVisitStatus">The line visit status.</param>
internal CodeFile(string path, int[] lineCoverage, LineVisitStatus[] lineVisitStatus)
: this(path, lineCoverage, lineVisitStatus, null)
: this(path, lineCoverage, lineVisitStatus, null, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CodeFile" /> class.
/// </summary>
/// <param name="path">The path of the file.</param>
/// <param name="lineCoverage">The line coverage.</param>
/// <param name="lineVisitStatus">The line visit status.</param>
/// <param name="additionalFileReader">The optional additional file reader.</param>
internal CodeFile(string path, int[] lineCoverage, LineVisitStatus[] lineVisitStatus, IFileReader additionalFileReader)
: this(path, lineCoverage, lineVisitStatus, null, additionalFileReader)
{
}

Expand All @@ -69,6 +86,24 @@ internal CodeFile(string path, int[] lineCoverage, LineVisitStatus[] lineVisitSt
/// <param name="lineVisitStatus">The line visit status.</param>
/// <param name="branches">The branches.</param>
internal CodeFile(string path, int[] lineCoverage, LineVisitStatus[] lineVisitStatus, IDictionary<int, ICollection<Branch>> branches)
: this(path, lineCoverage, lineVisitStatus, branches, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="CodeFile" /> class.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="lineCoverage">The line coverage.</param>
/// <param name="lineVisitStatus">The line visit status.</param>
/// <param name="branches">The branches.</param>
/// <param name="additionalFileReader">The optional additional file reader.</param>
internal CodeFile(
string path,
int[] lineCoverage,
LineVisitStatus[] lineVisitStatus,
IDictionary<int, ICollection<Branch>> branches,
IFileReader additionalFileReader)
{
if (lineCoverage == null)
{
Expand All @@ -89,6 +124,7 @@ internal CodeFile(string path, int[] lineCoverage, LineVisitStatus[] lineVisitSt
this.lineCoverage = lineCoverage;
this.lineVisitStatus = lineVisitStatus;
this.branches = branches;
this.additionalFileReader = additionalFileReader;
}

/// <summary>
Expand Down Expand Up @@ -347,7 +383,19 @@ internal void AddCodeElement(CodeElement codeElement)
internal FileAnalysis AnalyzeFile(IFileReader fileReader)
{
string error = null;
string[] lines = fileReader.LoadFile(this.Path, out error);

string[] lines = null;

if (this.additionalFileReader != null)
{
lines = this.additionalFileReader.LoadFile(this.Path, out error);
}

if (this.additionalFileReader == null || error != null)
{
error = null;
lines = fileReader.LoadFile(this.Path, out error);
}

if (error != null)
{
Expand Down Expand Up @@ -548,6 +596,11 @@ internal void Merge(CodeFile file)
{
codeElement.ApplyMaximumCoverageQuota(this.CoverageQuota(codeElement.FirstLine, codeElement.LastLine));
}

if (file.additionalFileReader == null)
{
file.additionalFileReader = this.additionalFileReader;
}
}

/// <summary>
Expand Down
@@ -0,0 +1,89 @@
using System;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Text;
using Palmmedia.ReportGenerator.Core.Common;
using Palmmedia.ReportGenerator.Core.Properties;

namespace Palmmedia.ReportGenerator.Core.Parser.FileReading
{
/// <summary>
/// File reader for reading files from local disk.
/// </summary>
internal class AltCoverEmbeddedFileReader : IFileReader
{
/// <summary>
/// Line endings to split lines on Windows and Unix.
/// </summary>
private static readonly string[] LineEndings = new string[]
{
"\r\n",
"\n"
};

/// <summary>
/// The Base64 and deflate compressed file.
/// </summary>
private readonly string base64DeflateCompressedFile;

/// <summary>
/// Initializes a new instance of the <see cref="AltCoverEmbeddedFileReader" /> class.
/// </summary>
/// <param name="base64DeflateCompressedFile">The Base64 and deflate compressed file.</param>
public AltCoverEmbeddedFileReader(string base64DeflateCompressedFile)
{
if (base64DeflateCompressedFile == null)
{
throw new ArgumentNullException(nameof(base64DeflateCompressedFile));
}

this.base64DeflateCompressedFile = base64DeflateCompressedFile;
}

/// <summary>
/// Loads the file with the given path.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="error">Error message if file reading failed, otherwise <code>null</code>.</param>
/// <returns><code>null</code> if an error occurs, otherwise the lines of the file.</returns>
public string[] LoadFile(string path, out string error)
{
try
{
byte[] base64Decoded = Convert.FromBase64String(this.base64DeflateCompressedFile);
byte[] decompressed = this.Decompress(base64Decoded);
string content = Encoding.UTF8.GetString(decompressed);

string[] lines = content.Split(LineEndings, StringSplitOptions.None);

error = null;
return lines;
}
catch (Exception ex)
{
error = string.Format(CultureInfo.InvariantCulture, Resources.ErrorDuringReadingFile, path, ex.GetExceptionMessageForDisplay());
return null;
}
}

private byte[] Decompress(byte[] data)
{
byte[] decompressedArray = null;
using (MemoryStream decompressedStream = new MemoryStream())
{
using (MemoryStream compressStream = new MemoryStream(data))
{
using (DeflateStream deflateStream = new DeflateStream(compressStream, CompressionMode.Decompress))
{
deflateStream.CopyTo(decompressedStream);
}
}

decompressedArray = decompressedStream.ToArray();
}

return decompressedArray;
}
}
}

0 comments on commit 540cfdf

Please sign in to comment.