Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
784472d
Streamlined ETL ULT tests
markgalvan-intel Nov 4, 2025
fdb35ba
Removed now redundant test
markgalvan-intel Nov 4, 2025
c372d76
Better name creation method and filesystem handling for Debug csvs
markgalvan-intel Nov 6, 2025
17823ea
Fix for ULT ETL testing
markgalvan-intel Nov 6, 2025
6ac3288
Updated test cases
markgalvan-intel Nov 6, 2025
103d0d8
Updated CsvHelper to use exceptions instead of MSTest Assert
markgalvan-intel Nov 7, 2025
2cc928d
Removed single test run support for IPM ETL ULTs
markgalvan-intel Nov 10, 2025
f337998
Adding in test cases csv
markgalvan-intel Nov 10, 2025
fdba925
Streamlined ETL ULT tests
markgalvan-intel Nov 4, 2025
9eef358
Removed now redundant test
markgalvan-intel Nov 4, 2025
5304cf3
Better name creation method and filesystem handling for Debug csvs
markgalvan-intel Nov 6, 2025
4e4bb48
Fix for ULT ETL testing
markgalvan-intel Nov 6, 2025
44e1b8a
Updated test cases
markgalvan-intel Nov 6, 2025
b12ad58
Updated CsvHelper to use exceptions instead of MSTest Assert
markgalvan-intel Nov 7, 2025
c4ae125
Removed single test run support for IPM ETL ULTs
markgalvan-intel Nov 10, 2025
f735da1
Adding in test cases csv
markgalvan-intel Nov 10, 2025
98e11c0
Removed "-in" suffix when waiting on pipe availability
markgalvan-intel Dec 3, 2025
b5106c8
Increased timeout for large test run
markgalvan-intel Dec 3, 2025
5adb94c
Merge remote-tracking branch 'origin/feature/ipm-ult-test-update' int…
markgalvan-intel Dec 3, 2025
c55bbe0
Added generic template definition.
markgalvan-intel Dec 3, 2025
b94ff49
Merge branch 'main' into feature/ipm-ult-test-update
markgalvan-intel Apr 2, 2026
22a8a55
Fixes for running ETL tests
markgalvan-intel Apr 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 147 additions & 44 deletions IntelPresentMon/PresentMonAPI2Tests/CsvHelper.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2022-2023 Intel Corporation
// Copyright (C) 2022-2023 Intel Corporation
// SPDX-License-Identifier: MIT
#pragma once

Expand All @@ -15,8 +15,10 @@
#include <limits>
#include <optional>
#include <cctype>
#include <filesystem>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace fs = std::filesystem;

enum Header {
Header_Application,
Expand Down Expand Up @@ -184,6 +186,63 @@ std::wstring CreateErrorString(Header columnId, size_t line)
return errorMessage;
}

class CsvException : public std::runtime_error {
public:
explicit CsvException(const std::string& msg) : std::runtime_error(msg) {}
};

class CsvConversionException : public CsvException {
Header columnId_;
size_t line_;
std::string value_;
public:
CsvConversionException(Header columnId, size_t line, const std::string& value)
: CsvException(std::format("Invalid {} at line {}: '{}'",
GetHeaderString(columnId), line, value))
, columnId_(columnId)
, line_(line)
, value_(value)
{
}

Header GetColumnId() const { return columnId_; }
size_t GetLine() const { return line_; }
const std::string& GetValue() const { return value_; }
};

class CsvFileException : public CsvException {
public:
explicit CsvFileException(const std::string& msg) : CsvException(msg) {}
};

class CsvValidationException : public CsvException {
Header columnId_;
size_t line_;

// Helper to format values for error messages
template<typename T>
static auto FormatValue(const T& value) {
if constexpr (std::is_enum_v<T>) {
return static_cast<std::underlying_type_t<T>>(value);
} else {
return value;
}
}

public:
template<typename T>
CsvValidationException(Header columnId, size_t line, const T& receivedValue, const T& expectedValue)
: CsvException(std::format("{} at line {}: Do not match. Received value {}, Expected value {}",
GetHeaderString(columnId), line, FormatValue(receivedValue), FormatValue(expectedValue)))
, columnId_(columnId)
, line_(line)
{
}

Header GetColumnId() const { return columnId_; }
size_t GetLine() const { return line_; }
};

template <typename T>
class CharConvert {
public:
Expand All @@ -198,7 +257,7 @@ void CharConvert<T>::Convert(const std::string data, T& convertedData, Header co
convertedData = std::stod(data);
}
catch (...) {
Assert::Fail(CreateErrorString(columnId, line).c_str());
throw CsvConversionException(columnId, line, data);
}
}
else if constexpr (std::is_same<T, uint32_t>::value) {
Expand All @@ -207,7 +266,7 @@ void CharConvert<T>::Convert(const std::string data, T& convertedData, Header co
convertedData = std::stoul(data);
}
catch (...) {
Assert::Fail(CreateErrorString(columnId, line).c_str());
throw CsvConversionException(columnId, line, data);
}
}
else if constexpr (std::is_same<T, int32_t>::value) {
Expand All @@ -216,7 +275,7 @@ void CharConvert<T>::Convert(const std::string data, T& convertedData, Header co
convertedData = std::stoi(data);
}
catch (...) {
Assert::Fail(CreateErrorString(columnId, line).c_str());
throw CsvConversionException(columnId, line, data);
}
}
else if constexpr (std::is_same<T, uint64_t>::value) {
Expand All @@ -236,7 +295,7 @@ void CharConvert<T>::Convert(const std::string data, T& convertedData, Header co
convertedData = static_cast<uint64_t>(result1);
}
catch (...) {
Assert::Fail(CreateErrorString(columnId, line).c_str());
throw CsvConversionException(columnId, line, data);

}
}
Expand All @@ -251,7 +310,7 @@ void CharConvert<T>::Convert(const std::string data, T& convertedData, Header co
convertedData = PM_GRAPHICS_RUNTIME_UNKNOWN;
}
else {
Assert::Fail(CreateErrorString(columnId, line).c_str());
throw CsvConversionException(columnId, line, data);
}

}
Expand Down Expand Up @@ -281,7 +340,7 @@ void CharConvert<T>::Convert(const std::string data, T& convertedData, Header co
convertedData = PM_PRESENT_MODE_UNKNOWN;
}
else {
Assert::Fail(CreateErrorString(Header_PresentMode, line).c_str());
throw CsvConversionException(Header_PresentMode, line, data);
}
}
else if constexpr (std::is_same<T, PM_FRAME_TYPE>::value) {
Expand All @@ -304,12 +363,12 @@ void CharConvert<T>::Convert(const std::string data, T& convertedData, Header co
convertedData = PM_FRAME_TYPE_INTEL_XEFG;
}
else {
Assert::Fail(CreateErrorString(Header_FrameType, line).c_str());
throw CsvConversionException(Header_FrameType, line, data);
}
}
else
{
Assert::Fail(CreateErrorString(UnknownHeader, line).c_str());
throw CsvException("Unknown type conversion requested");
}
}

Expand Down Expand Up @@ -394,6 +453,44 @@ bool ValidateFrameType(PM_FRAME_TYPE gold, PM_FRAME_TYPE test) {

}

template<typename T>
void ValidateOrThrow(Header columnId, size_t line, const T& received, const T& expected) {
if constexpr (std::is_same_v<T, double>) {
// Both NaN is considered a match (both represent missing/invalid data)
if (std::isnan(received) && std::isnan(expected)) {
return;
}
// One NaN and one value is a mismatch
if (std::isnan(received) || std::isnan(expected)) {
throw CsvValidationException(columnId, line, received, expected);
}
// Both are valid numbers, compare with threshold
auto min = std::min(countDecimalPlaces(received), countDecimalPlaces(expected));
double threshold = pow(0.1, min);
double difference = received - expected;
if (difference <= -threshold || difference >= threshold) {
throw CsvValidationException(columnId, line, received, expected);
}
}
else {
if (received != expected) {
throw CsvValidationException(columnId, line, received, expected);
}
}
}

// Overload for std::optional - treats empty optional as NaN
void ValidateOrThrow(Header columnId, size_t line, const std::optional<double>& received, double expected) {
if (received.has_value()) {
ValidateOrThrow(columnId, line, received.value(), expected);
} else {
// No value in CSV (optional is empty), expected should be NaN
if (!std::isnan(expected)) {
throw CsvValidationException(columnId, line, std::nan(""), expected);
}
}
}

template<typename T>
bool Validate(const T& param1, const T& param2) {
if constexpr (std::is_same<T, double>::value) {
Expand Down Expand Up @@ -441,10 +538,25 @@ std::optional<std::ofstream> CreateCsvFile(std::string& output_dir, std::string&
time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
tm local_time;
localtime_s(&local_time, &now);
std::ofstream csvFile;
std::string csvFileName = output_dir + processName;

try {
csvFile.open(csvFileName);
// Use filesystem to construct the path properly
fs::path outputPath(output_dir);
fs::path fileName = processName + "_" +
std::to_string(local_time.tm_year + 1900) +
std::to_string(local_time.tm_mon + 1) +
std::to_string(local_time.tm_mday) +
std::to_string(local_time.tm_hour) +
std::to_string(local_time.tm_min) +
std::to_string(local_time.tm_sec) + ".csv";

fs::path fullPath = outputPath / fileName;

std::ofstream csvFile(fullPath);
if (!csvFile.is_open()) {
return std::nullopt;
}

csvFile <<
"Application,ProcessID,SwapChainAddress,PresentRuntime"
",SyncInterval,PresentFlags,AllowsTearing,PresentMode"
Expand Down Expand Up @@ -684,37 +796,37 @@ bool CsvParser::VerifyBlobAgainstCsv(const std::string& processName, const unsig
switch (pair.second)
{
case Header_Application:
columnsMatch = true;
// Skip validation for application name
break;
case Header_ProcessID:
columnsMatch = Validate(v2MetricRow_.processId, processId_);
ValidateOrThrow(pair.second, line_, v2MetricRow_.processId, processId_);
break;
case Header_SwapChainAddress:
columnsMatch = Validate(v2MetricRow_.swapChain, swapChain);
ValidateOrThrow(pair.second, line_, v2MetricRow_.swapChain, swapChain);
break;
case Header_Runtime:
columnsMatch = Validate(v2MetricRow_.runtime, graphicsRuntime);
ValidateOrThrow(pair.second, line_, v2MetricRow_.runtime, graphicsRuntime);
break;
case Header_SyncInterval:
columnsMatch = Validate(v2MetricRow_.syncInterval, syncInterval);
ValidateOrThrow(pair.second, line_, v2MetricRow_.syncInterval, syncInterval);
break;
case Header_PresentFlags:
columnsMatch = Validate(v2MetricRow_.presentFlags, presentFlags);
ValidateOrThrow(pair.second, line_, v2MetricRow_.presentFlags, presentFlags);
break;
case Header_AllowsTearing:
columnsMatch = Validate(v2MetricRow_.allowsTearing, (uint32_t)allowsTearing);
ValidateOrThrow(pair.second, line_, v2MetricRow_.allowsTearing, (uint32_t)allowsTearing);
break;
case Header_PresentMode:
columnsMatch = Validate(v2MetricRow_.presentMode, presentMode);
ValidateOrThrow(pair.second, line_, v2MetricRow_.presentMode, presentMode);
break;
case Header_FrameType:
columnsMatch = ValidateFrameType(v2MetricRow_.frameType, frameType);
break;
case Header_TimeInSeconds:
columnsMatch = Validate(v2MetricRow_.presentStartQPC, timeQpc);
ValidateOrThrow(pair.second, line_, v2MetricRow_.presentStartQPC, timeQpc);
break;
case Header_CPUStartQPC:
columnsMatch = Validate(v2MetricRow_.cpuFrameQpc, cpuStartQpc);
ValidateOrThrow(pair.second, line_, v2MetricRow_.cpuFrameQpc, cpuStartQpc);
break;
case Header_MsBetweenAppStart:
columnsMatch = ValidateMetricValue(v2MetricRow_.msBetweenAppStart, msBetweenAppStart);
Expand All @@ -726,16 +838,16 @@ bool CsvParser::VerifyBlobAgainstCsv(const std::string& processName, const unsig
columnsMatch = ValidateMetricValue(v2MetricRow_.msCpuWait, msCpuWait);
break;
case Header_MsGPULatency:
columnsMatch = Validate(v2MetricRow_.msGpuLatency, msGpuLatency);
ValidateOrThrow(pair.second, line_, v2MetricRow_.msGpuLatency, msGpuLatency);
break;
case Header_MsGPUTime:
columnsMatch = Validate(v2MetricRow_.msGpuTime, msGpuTime);
ValidateOrThrow(pair.second, line_, v2MetricRow_.msGpuTime, msGpuTime);
break;
case Header_MsGPUBusy:
columnsMatch = Validate(v2MetricRow_.msGpuBusy, msGpuBusy);
ValidateOrThrow(pair.second, line_, v2MetricRow_.msGpuBusy, msGpuBusy);
break;
case Header_MsGPUWait:
columnsMatch = Validate(v2MetricRow_.msGpuWait, msGpuWait);
ValidateOrThrow(pair.second, line_, v2MetricRow_.msGpuWait, msGpuWait);
break;
case Header_MsBetweenSimulationStart:
columnsMatch = ValidateMetricValue(v2MetricRow_.msBetweenSimStart, msBetweenSimStartTime);
Expand Down Expand Up @@ -768,15 +880,9 @@ bool CsvParser::VerifyBlobAgainstCsv(const std::string& processName, const unsig
columnsMatch = ValidateMetricValue(v2MetricRow_.msInstrumentedLatency, msInstrumentedLatency);
break;
default:
columnsMatch = true;
// Unknown header, skip validation
break;
}
if (columnsMatch == false) {
// If the columns do not match, create an error string
// and assert failure.
Assert::Fail(CreateErrorString(pair.second, line_).c_str());
}
Assert::IsTrue(columnsMatch, CreateErrorString(pair.second, line_).c_str());
}
}

Expand Down Expand Up @@ -823,7 +929,8 @@ bool CsvParser::Open(std::wstring const& path, uint32_t processId) {
cols_.clear();

if (_wfopen_s(&fp_, path.c_str(), L"r")) {
return false;
throw CsvFileException(std::format("Failed to open CSV file: {}",
pmon::util::str::ToNarrow(path)));
}

// Remove UTF-8 marker if there is one.
Expand All @@ -849,18 +956,15 @@ bool CsvParser::Open(std::wstring const& path, uint32_t processId) {
default:
if ((size_t)h < KnownHeaderCount) {
if (headerColumnIndex_[(size_t)h] != SIZE_MAX) {
std::wstring errorMessage = L"Duplicate column: ";
errorMessage += pmon::util::str::ToWide(cols_[i]);
Assert::Fail(errorMessage.c_str());
throw CsvFileException(std::format("Duplication column: {}", cols_[i]));
}
else {
headerColumnIndex_[(size_t)h] = i;
}
break;
}
else {
std::wstring errorMessage = L"Index outside of known headers.";
Assert::Fail(errorMessage.c_str());
throw CsvFileException("Index outside of known headers");
}
}
}
Expand Down Expand Up @@ -895,7 +999,7 @@ bool CsvParser::Open(std::wstring const& path, uint32_t processId) {
Header_MsPCLatency});

if (!columnsOK) {
Assert::Fail(L"Missing required columns");
throw CsvFileException("Missing required columns in CSV file");
}

// Create a vector of active headers to be used when reading
Expand Down Expand Up @@ -1113,7 +1217,7 @@ void CsvParser::ConvertToMetricDataType(const char* data, Header columnId)
}
break;
default:
Assert::Fail(CreateErrorString(UnknownHeader, line_).c_str());
throw CsvConversionException(UnknownHeader, line_, data);
}

return;
Expand All @@ -1127,9 +1231,8 @@ bool CsvParser::ReadRow(bool gatherMetrics)
// Read a line
if (fgets(row_, _countof(row_), fp_) == nullptr) {
if (ferror(fp_) != 0) {
std::wstring errorMessage = L"File read error at line: ";
errorMessage += std::to_wstring(line_);
Assert::Fail(errorMessage.c_str());
throw CsvFileException(std::format("File read error at line: {}",
std::to_string(line_)));
}
return false;
}
Expand Down
Loading