From 590610137bbf30aa148d2585e52b093b3251b1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Antoniak?= <47522782+MAntoniak@users.noreply.github.com> Date: Fri, 19 Jun 2026 22:04:26 +0200 Subject: [PATCH 1/3] Move implementation of FileCoverageV2's method to cpp file. --- Coverage/FileCoverageV2.cpp | 105 ++++++++++++++++++++++ Coverage/FileCoverageV2.h | 111 +++--------------------- Coverage/Shared/Shared.vcxitems | 3 +- Coverage/Shared/Shared.vcxitems.filters | 1 + 4 files changed, 118 insertions(+), 102 deletions(-) create mode 100644 Coverage/FileCoverageV2.cpp diff --git a/Coverage/FileCoverageV2.cpp b/Coverage/FileCoverageV2.cpp new file mode 100644 index 0000000..5c62326 --- /dev/null +++ b/Coverage/FileCoverageV2.cpp @@ -0,0 +1,105 @@ +#include "FileCoverageV2.h" +#include "base64.h" + +#include + +FileCoverageV2::FileCoverageV2(size_t nbLines) : + _nbLinesFile(nbLines) +{ + _code.resize(nbLines); +} + +FileCoverageV2::LineArray::value_type FileCoverageV2::encodeLine(bool isCode, const FileLineInfo& line) +{ + LineArray::value_type code = 0; + if (isCode) + { + // | 15 | 14 | 13 ---- 0 | + // | is code | is partial | count | + if (line.DebugCount > 0) + { + code |= FileCoverageV2::maskIsCode; + _nbLinesCode += 1; + } + if (line.HitCount > 0) + { + if (line.DebugCount != line.HitCount) + { + code |= FileCoverageV2::maskIsPartial; + } + _nbLinesCovered += 1; + } + code |= std::min(line.HitCount, FileCoverageV2::maskCount); + } + return code; +} + +void FileCoverageV2::updateStats() +{ + _nbLinesCovered = 0; + for (const auto& line : _code) + { + if ((line & maskIsCode) == maskIsCode) + { + if ((line & maskCount) > 0) + { + _nbLinesCovered += 1; + } + } + } +} + +bool FileCoverageV2::merge(const FileCoverageV2& other) +{ + if (_code.size() != other._code.size()) + return false; + + auto src = other._code.cbegin(); + + for (auto& line : _code) + { + const size_t count = (size_t) (line & maskCount) + (size_t) (*src & maskCount); + + const bool isCode = (line & maskIsCode) == maskIsCode; + const bool isPartial = (line & maskIsPartial) == maskIsPartial && (*src & maskIsPartial) == maskIsPartial; + + line = (uint16_t) std::min(count, maskCount); + line |= isCode ? maskIsCode : 0; + line |= isPartial ? maskIsPartial : 0; + + ++src; + } + updateStats(); + return true; +} + +void FileCoverageV2::writeHeader(std::ostream& ofs) +{ + const std::string version("2.0"); + + ofs << R"()" << std::endl; + ofs << std::format(R"()", version) << std::endl; +} + +void FileCoverageV2::openDirectory(std::ostream& ofs, const std::string& aDir) +{ + ofs << std::format(R"( )", aDir) << std::endl; +} + +void FileCoverageV2::closeDirectory(std::ostream& ofs) +{ + ofs << " " << std::endl; +} + +void FileCoverageV2::writeFooter(std::ostream& ofs) +{ + ofs << "" << std::endl; +} + +void FileCoverageV2::write(const std::string& filepath, std::ostream& ofs) const +{ + ofs << std::format(R"( )", filepath, md5Code) << std::endl; + ofs << std::format(R"( )", _nbLinesFile, _nbLinesCode, _nbLinesCovered) << std::endl; + ofs << R"( )" << Base64::Encode(std::string(reinterpret_cast(_code.data()), _code.size() * sizeof(LineArray::value_type))) << "" << std::endl; + ofs << R"( )" << std::endl; +} \ No newline at end of file diff --git a/Coverage/FileCoverageV2.h b/Coverage/FileCoverageV2.h index 8f20416..6742dfb 100644 --- a/Coverage/FileCoverageV2.h +++ b/Coverage/FileCoverageV2.h @@ -1,10 +1,8 @@ #pragma once -#include "base64.h" #include "FileInfo.h" -#include -#include +#include struct FileCoverageV2 { @@ -19,104 +17,15 @@ struct FileCoverageV2 size_t _nbLinesCovered = 0; std::string md5Code; - FileCoverageV2(size_t nbLines = 0) : - _nbLinesFile(nbLines) - { - _code.resize(nbLines); - } + FileCoverageV2(size_t nbLines = 0); - LineArray::value_type encodeLine(bool isCode, const FileLineInfo& line) - { - LineArray::value_type code = 0; - if (isCode) - { - // | 15 | 14 | 13 ---- 0 | - // | is code | is partial | count | - if (line.DebugCount > 0) - { - code |= FileCoverageV2::maskIsCode; - _nbLinesCode += 1; - } - if (line.HitCount > 0) - { - if (line.DebugCount != line.HitCount) - { - code |= FileCoverageV2::maskIsPartial; - } - _nbLinesCovered += 1; - } - code |= std::min(line.HitCount, FileCoverageV2::maskCount); - } - return code; - } + LineArray::value_type encodeLine(bool isCode, const FileLineInfo& line); + void updateStats(); + bool merge(const FileCoverageV2& other); - void updateStats() - { - _nbLinesCovered = 0; - for (const auto& line : _code) - { - if ((line & maskIsCode) == maskIsCode) - { - if ((line & maskCount) > 0) - { - _nbLinesCovered += 1; - } - } - } - } - - bool merge(const FileCoverageV2& other) - { - if (_code.size() != other._code.size()) - return false; - - auto src = other._code.cbegin(); - - for (auto& line : _code) - { - const size_t count = (size_t) (line & maskCount) + (size_t) (*src & maskCount); - - const bool isCode = (line & maskIsCode) == maskIsCode; - const bool isPartial = (line & maskIsPartial) == maskIsPartial && (*src & maskIsPartial) == maskIsPartial; - - line = (uint16_t) std::min(count, maskCount); - line |= isCode ? maskIsCode : 0; - line |= isPartial ? maskIsPartial : 0; - - ++src; - } - updateStats(); - return true; - } - - static void writeHeader(std::ostream& ofs) - { - const std::string version("2.0"); - - ofs << R"()" << std::endl; - ofs << std::format(R"()", version) << std::endl; - } - - static void openDirectory(std::ostream& ofs, const std::string& aDir) - { - ofs << std::format(R"( )", aDir) << std::endl; - } - - static void closeDirectory(std::ostream& ofs) - { - ofs << " " << std::endl; - } - - static void writeFooter(std::ostream& ofs) - { - ofs << "" << std::endl; - } - - void write(const std::string& filepath, std::ostream& ofs) const - { - ofs << std::format(R"( )", filepath, md5Code) << std::endl; - ofs << std::format(R"( )", _nbLinesFile, _nbLinesCode, _nbLinesCovered) << std::endl; - ofs << R"( )" << Base64::Encode(std::string(reinterpret_cast(_code.data()), _code.size() * sizeof(LineArray::value_type))) << "" << std::endl; - ofs << R"( )" << std::endl; - } + static void writeHeader(std::ostream& ofs); + static void openDirectory(std::ostream& ofs, const std::string& aDir); + static void closeDirectory(std::ostream& ofs); + static void writeFooter(std::ostream& ofs); + void write(const std::string& filepath, std::ostream& ofs) const; }; \ No newline at end of file diff --git a/Coverage/Shared/Shared.vcxitems b/Coverage/Shared/Shared.vcxitems index b022d07..e5f6396 100644 --- a/Coverage/Shared/Shared.vcxitems +++ b/Coverage/Shared/Shared.vcxitems @@ -1,4 +1,4 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) @@ -45,6 +45,7 @@ + diff --git a/Coverage/Shared/Shared.vcxitems.filters b/Coverage/Shared/Shared.vcxitems.filters index 535b217..88589aa 100644 --- a/Coverage/Shared/Shared.vcxitems.filters +++ b/Coverage/Shared/Shared.vcxitems.filters @@ -12,6 +12,7 @@ + From 00c96894ca1cf76e6f28e45ea73f152550c0d7b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Antoniak?= <47522782+MAntoniak@users.noreply.github.com> Date: Thu, 25 Jun 2026 00:01:08 +0200 Subject: [PATCH 2/3] Fix the update line count for the FileCoverageV2 object after merging with another object --- Coverage/FileCoverageV2.cpp | 6 ++- Coverage/Test/FileCoverageV2Test.cpp | 59 ++++++++++++++++++++++++++++ Coverage/Test/Test.vcxproj | 1 + 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 Coverage/Test/FileCoverageV2Test.cpp diff --git a/Coverage/FileCoverageV2.cpp b/Coverage/FileCoverageV2.cpp index 5c62326..264b319 100644 --- a/Coverage/FileCoverageV2.cpp +++ b/Coverage/FileCoverageV2.cpp @@ -1,6 +1,7 @@ #include "FileCoverageV2.h" #include "base64.h" +#include #include FileCoverageV2::FileCoverageV2(size_t nbLines) : @@ -37,16 +38,19 @@ FileCoverageV2::LineArray::value_type FileCoverageV2::encodeLine(bool isCode, co void FileCoverageV2::updateStats() { _nbLinesCovered = 0; + _nbLinesCode = 0; for (const auto& line : _code) { if ((line & maskIsCode) == maskIsCode) { + _nbLinesCode++; if ((line & maskCount) > 0) { - _nbLinesCovered += 1; + _nbLinesCovered++; } } } + assert(_nbLinesCovered <= _nbLinesCode); } bool FileCoverageV2::merge(const FileCoverageV2& other) diff --git a/Coverage/Test/FileCoverageV2Test.cpp b/Coverage/Test/FileCoverageV2Test.cpp new file mode 100644 index 0000000..26ff616 --- /dev/null +++ b/Coverage/Test/FileCoverageV2Test.cpp @@ -0,0 +1,59 @@ +#include "CppUnitTest.h" +#include + +#include "FileCoverageV2.h" + +#ifndef NOMINMAX +# define NOMINMAX +# include +#endif + +#pragma warning(disable: 4091) +#include +#pragma warning(default: 4091) + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace Microsoft +{ + namespace VisualStudio + { + namespace CppUnitTestFramework + { + template<> static std::wstring ToString(const class FileCoverageV2::LineArray& t) { return L"FileCoverageV2::LineArray"; } + } + } +} + +namespace TestNativeV2 +{ + TEST_CLASS(FileCoverage) + { + public: + + TEST_METHOD(WriteTest) + { + const auto max = FileCoverageV2::maskCount; + const auto c = FileCoverageV2::maskIsCode; + const auto p = FileCoverageV2::maskIsPartial; + FileCoverageV2 coverage(9); + coverage.md5Code = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + coverage._code = { 0, 0, c, c, c | p | 1, c | 10000, c | 3000, 0, c | p | 1 }; + coverage.updateStats(); + + Assert::AreEqual(9u, coverage._nbLinesFile); + Assert::AreEqual(6u, coverage._nbLinesCode); + Assert::AreEqual(4u, coverage._nbLinesCovered); + + static constexpr char EXPECT_STREAM[] = + " \n" + " \n" + " AAAAAACAAIABwBCnuIsAAAHA\n" + " \n"; + + std::stringstream ss; + coverage.write("filename", ss); + Assert::AreEqual(EXPECT_STREAM, ss.str().c_str()); + } + }; +} \ No newline at end of file diff --git a/Coverage/Test/Test.vcxproj b/Coverage/Test/Test.vcxproj index b784939..51795f4 100644 --- a/Coverage/Test/Test.vcxproj +++ b/Coverage/Test/Test.vcxproj @@ -169,6 +169,7 @@ + From 2f44ee740e93f077b764189652c6b6142b6df4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Antoniak?= <47522782+MAntoniak@users.noreply.github.com> Date: Thu, 25 Jun 2026 01:12:40 +0200 Subject: [PATCH 3/3] Mark the line as code if it has already been marked as such in any of the reports. --- Coverage/FileCoverageV2.cpp | 2 +- Coverage/Test/FileCoverageV2Test.cpp | 58 ++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/Coverage/FileCoverageV2.cpp b/Coverage/FileCoverageV2.cpp index 264b319..5d889f7 100644 --- a/Coverage/FileCoverageV2.cpp +++ b/Coverage/FileCoverageV2.cpp @@ -64,7 +64,7 @@ bool FileCoverageV2::merge(const FileCoverageV2& other) { const size_t count = (size_t) (line & maskCount) + (size_t) (*src & maskCount); - const bool isCode = (line & maskIsCode) == maskIsCode; + const bool isCode = ((line & maskIsCode) | (*src & maskIsCode)) == maskIsCode || count > 0; const bool isPartial = (line & maskIsPartial) == maskIsPartial && (*src & maskIsPartial) == maskIsPartial; line = (uint16_t) std::min(count, maskCount); diff --git a/Coverage/Test/FileCoverageV2Test.cpp b/Coverage/Test/FileCoverageV2Test.cpp index 26ff616..41062a8 100644 --- a/Coverage/Test/FileCoverageV2Test.cpp +++ b/Coverage/Test/FileCoverageV2Test.cpp @@ -29,13 +29,13 @@ namespace TestNativeV2 { TEST_CLASS(FileCoverage) { + static constexpr auto max = FileCoverageV2::maskCount; + static constexpr auto c = FileCoverageV2::maskIsCode; + static constexpr auto p = FileCoverageV2::maskIsPartial; public: TEST_METHOD(WriteTest) { - const auto max = FileCoverageV2::maskCount; - const auto c = FileCoverageV2::maskIsCode; - const auto p = FileCoverageV2::maskIsPartial; FileCoverageV2 coverage(9); coverage.md5Code = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; coverage._code = { 0, 0, c, c, c | p | 1, c | 10000, c | 3000, 0, c | p | 1 }; @@ -55,5 +55,57 @@ namespace TestNativeV2 coverage.write("filename", ss); Assert::AreEqual(EXPECT_STREAM, ss.str().c_str()); } + + TEST_METHOD(MergeTest) + { + FileCoverageV2 coverage(9); + coverage.md5Code = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + coverage._code = { 0, 0, c, c, c | p | 1, c | 10000, c | 3000, 0, c }; + coverage.updateStats(); + + FileCoverageV2 other(9); + other.md5Code = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + other._code = { 10, c | p | 20, c, c, c | p | 4, c | p | 50, c | 3000, c | 8, c | 2 }; + other.updateStats(); + + auto result = coverage.merge(other); + Assert::IsTrue(result); + + static constexpr char EXPECT_STREAM[] = + " \n" + " \n" + " CoAUgACAAIAFwEKncJcIgAKA\n" + " \n"; + + std::stringstream ss; + coverage.write("filename", ss); + Assert::AreEqual(EXPECT_STREAM, ss.str().c_str()); + } + + TEST_METHOD(MergeDifferentLinesCountTest) + { + FileCoverageV2 coverage(3); + coverage.md5Code = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + coverage._code = { 0, 0, c }; + coverage.updateStats(); + + FileCoverageV2 other(9); + other.md5Code = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + other._code = { 10, c | p | 20, c, c, c | p | 4, c | p | 50, c | 3000, c | 8, c | 2 }; + other.updateStats(); + + auto result = coverage.merge(other); + Assert::IsFalse(result); + + static constexpr char EXPECT_STREAM[] = + " \n" + " \n" + " AAAAAACA\n" + " \n"; + + std::stringstream ss; + coverage.write("filename", ss); + Assert::AreEqual(EXPECT_STREAM, ss.str().c_str()); + } }; } \ No newline at end of file