Files
UnrealEngine/Engine/Source/Developer/AutomationController/Private/AutomationReportManager.cpp
2025-05-18 13:04:45 +08:00

274 lines
8.8 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AutomationReportManager.h"
#include "HAL/FileManager.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "IAutomationControllerModule.h"
#include "AutomationReport.h"
namespace
{
// Note that if you change at least one line here you must change other lines in order to keep consistent columns count.
// Header string to add as a report's first line
constexpr TCHAR ReportHeader[] = TEXT("\"GameInstance\",\"Platform\",\"Test Name\",\"Duration\",\"Status\"");
// Format string that is used to fill the lines that contain report's data
constexpr TCHAR ReportLineFormatString[] = TEXT("\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"");
// Pre and post fix strings to add to the errors and warnings for viewing in CSV. Spaces correctly, and allows commas in the message
constexpr TCHAR ErrorReportLinePrefixText[] = TEXT("\t,,,,\"");
constexpr TCHAR ErrorReportLineSufixText[] = TEXT("\"");
} // anonymous namespace
FAutomationReportManager::FAutomationReportManager()
{
//ensure that we have a valid root to the hierarchy
FAutomationTestInfo TestInfo;
ReportRoot = MakeShareable(new FAutomationReport( TestInfo ));
}
void FAutomationReportManager::Empty()
{
//ensure there is a root node
ReportRoot->Empty();
}
void FAutomationReportManager::ClustersUpdated( const int32 NumClusters )
{
ReportRoot->ClustersUpdated(NumClusters);
}
void FAutomationReportManager::ResetForExecution(const int32 NumTestPasses)
{
//Recursively prepare all tests for execution
ReportRoot->ResetForExecution(NumTestPasses);
}
void FAutomationReportManager::StopRunningTests()
{
ReportRoot->StopRunningTest();
}
TSharedPtr<IAutomationReport> FAutomationReportManager::GetNextReportToExecute(bool& bOutAllTestsComplete, const int32 ClusterIndex, const int32 PassIndex, const int32 NumDevicesInCluster)
{
TSharedPtr<IAutomationReport> ReportToExecute = ReportRoot->GetNextReportToExecute(bOutAllTestsComplete, ClusterIndex, PassIndex, NumDevicesInCluster);
return ReportToExecute;
}
TArray<TSharedPtr<IAutomationReport>> FAutomationReportManager::GetEnabledTestReports()
{
TArray<TSharedPtr<IAutomationReport>> Reports;
ReportRoot->GetEnabledTestReports(Reports);
return Reports;
}
TSharedPtr<IAutomationReport> FAutomationReportManager::EnsureReportExists(FAutomationTestInfo& TestInfo, const int32 ClusterIndex, const int32 NumPasses)
{
TSharedPtr<IAutomationReport> NewStatus = ReportRoot->EnsureReportExists (TestInfo, ClusterIndex, NumPasses);
return NewStatus;
}
void FAutomationReportManager::SetFilter( TSharedPtr< AutomationFilterCollection > InFilter )
{
ReportRoot->SetFilter( InFilter );
}
TArray <TSharedPtr <IAutomationReport> >& FAutomationReportManager::GetFilteredReports()
{
return ReportRoot->GetFilteredChildren();
}
void FAutomationReportManager::SetVisibleTestsEnabled(const bool bEnabled)
{
ReportRoot->SetEnabled(bEnabled);
}
int32 FAutomationReportManager::GetEnabledTestsNum() const
{
return ReportRoot->GetEnabledTestsNum();
}
void FAutomationReportManager::GetEnabledTestNames(TArray<FString>& OutEnabledTestNames) const
{
ReportRoot->GetEnabledTestNames(OutEnabledTestNames,FString());
}
void FAutomationReportManager::GetFilteredTestNames(TArray<FString>& OutFilteredTestNames) const
{
ReportRoot->GetFilteredTestNames(OutFilteredTestNames, FString());
}
void FAutomationReportManager::SetEnabledTests(const TArray<FString>& EnabledTests)
{
ReportRoot->SetEnabledTests(EnabledTests,FString());
}
const bool FAutomationReportManager::ExportReport(uint32 FileExportTypeMask, const int32 NumDeviceClusters)
{
// The results log. Errors, warnings etc are added to this
TArray<FString> ResultsLog = { ReportHeader };
// Create the report be recursively going through the results tree
FindLeafReport(ReportRoot, NumDeviceClusters, ResultsLog, FileExportTypeMask);
// Has a report been generated
bool ReportGenerated = false;
// Ensure we have a log to write (the first line is a header line)
if (ResultsLog.Num() > 1)
{
// Create the file name
FString FileName = FString::Printf( TEXT( "Automation%s.csv" ), *FDateTime::Now().ToString() );
FString FileLocation = FPaths::ConvertRelativePathToFull(FPaths::AutomationDir());
FString FullPath = FString::Printf( TEXT( "%s%s" ), *FileLocation, *FileName );
// save file
FArchive* LogFile = IFileManager::Get().CreateFileWriter( *FullPath );
if (LogFile != NULL)
{
for (int32 Index = 0; Index < ResultsLog.Num(); ++Index)
{
FString LogEntry = FString::Printf( TEXT( "%s" ),
*ResultsLog[ Index ]) + LINE_TERMINATOR;
LogFile->Serialize( TCHAR_TO_ANSI( *LogEntry ), LogEntry.Len() );
}
LogFile->Close();
delete LogFile;
// A report has been generated
ReportGenerated = true;
}
}
return ReportGenerated;
}
void FAutomationReportManager::AddResultReport(TSharedPtr< IAutomationReport > InReport, const int32 NumDeviceClusters, TArray< FString >& ResultsLog, uint32 FileExportTypeMask)
{
if (InReport->IsEnabled())
{
for (int32 ClusterIndex = 0; ClusterIndex < NumDeviceClusters; ++ClusterIndex)
{
FAutomationTestResults TestResults = InReport->GetResults( ClusterIndex,CurrentTestPass );
// Early out if we don't want this report
bool AddReport = true;
if ( !EFileExportType::IsSet( FileExportTypeMask, EFileExportType::FET_All ) && !EFileExportType::IsSet( FileExportTypeMask, EFileExportType::FET_Status ) )
{
uint32 ResultMask = 0;
if (TestResults.GetErrorTotal() > 0)
{
EFileExportType::SetFlag( ResultMask, EFileExportType::FET_Errors );
}
if (TestResults.GetWarningTotal() > 0)
{
EFileExportType::SetFlag( ResultMask, EFileExportType::FET_Warnings );
}
if (TestResults.GetLogTotal())
{
EFileExportType::SetFlag( ResultMask, EFileExportType::FET_Logs );
}
// Ensure we have a report that passes at least one filter
if ( ( ResultMask & FileExportTypeMask ) == 0 )
{
AddReport = false;
}
}
if (AddReport)
{
// Get the duration of the test
float MinDuration;
float MaxDuration;
FString DurationString;
if (InReport->GetDurationRange(MinDuration, MaxDuration ))
{
//if there is a duration range
if (MinDuration != MaxDuration)
{
DurationString = FString::Printf( TEXT( "%4.4fs - %4.4fs" ), MinDuration, MaxDuration );
}
else
{
DurationString = FString::Printf( TEXT( "%4.4fs" ), MinDuration );
}
}
// Build a status string that contains information about the test
FString Status = AutomationStateToString(TestResults.State);
// Create the log string
FString Info = FString::Printf( ReportLineFormatString,
*InReport->GetGameInstanceName( ClusterIndex ),
*FModuleManager::GetModuleChecked< IAutomationControllerModule >( "AutomationController" ).GetAutomationController()->GetDeviceTypeName( ClusterIndex ),
*InReport->GetDisplayNameWithDecoration(),
*DurationString,
*Status );
ResultsLog.Add( Info );
// Add any warning or errors
for ( const FAutomationExecutionEntry& Entry : TestResults.GetEntries() )
{
switch (Entry.Event.Type )
{
case EAutomationEventType::Info:
if ( EFileExportType::IsSet(FileExportTypeMask, EFileExportType::FET_All) || EFileExportType::IsSet(FileExportTypeMask, EFileExportType::FET_Logs) )
{
ResultsLog.Add(ErrorReportLinePrefixText + Entry.ToString() + ErrorReportLineSufixText);
}
break;
case EAutomationEventType::Warning:
if ( EFileExportType::IsSet(FileExportTypeMask, EFileExportType::FET_All) || EFileExportType::IsSet(FileExportTypeMask, EFileExportType::FET_Warnings) )
{
ResultsLog.Add(ErrorReportLinePrefixText + Entry.ToString() + ErrorReportLineSufixText);
}
break;
case EAutomationEventType::Error:
if ( EFileExportType::IsSet(FileExportTypeMask, EFileExportType::FET_All) || EFileExportType::IsSet(FileExportTypeMask, EFileExportType::FET_Errors) )
{
ResultsLog.Add(ErrorReportLinePrefixText + Entry.ToString() + ErrorReportLineSufixText);
}
break;
}
}
}
}
}
}
void FAutomationReportManager::FindLeafReport(TSharedPtr< IAutomationReport > InReport, const int32 NumDeviceClusters, TArray< FString >& ResultsLog, uint32 FileExportTypeMask)
{
TArray< TSharedPtr< IAutomationReport > >& ChildReports = InReport->GetFilteredChildren();
// If there are no child reports, we have reached a leaf that has had a test run on it.
if (ChildReports.Num() == 0)
{
AddResultReport( InReport, NumDeviceClusters, ResultsLog, FileExportTypeMask );
}
else
{
// We still have some child nodes. We won't have run a test on this node
for (int32 ChildIndex = 0; ChildIndex < ChildReports.Num(); ++ChildIndex)
{
FindLeafReport( ChildReports[ ChildIndex ], NumDeviceClusters, ResultsLog, FileExportTypeMask );
}
}
}