From c2a6addf1ff60dfed88c19d3ceb5f00937bcc904 Mon Sep 17 00:00:00 2001 From: alexpete Date: Tue, 20 Apr 2021 10:42:39 -0700 Subject: [PATCH] Integrating latest from github/TIF/Runtime --- .../Console/Code/Source/TestImpactConsole.cpp | 20 +- .../Runtime/Code/CMakeLists.txt | 64 +- .../TestImpactFramework/TestImpactBitwise.h | 35 + .../TestImpactFramework/TestImpactCallback.h | 23 + .../TestImpactFramework/TestImpactException.h | 51 + .../TestImpactFrameworkPath.h | 41 + .../Artifact/Dynamic/TestImpactChangeList.h | 27 + .../Artifact/Dynamic/TestImpactCoverage.h | 41 + .../Dynamic/TestImpactTestEnumerationSuite.h | 21 + .../Artifact/Dynamic/TestImpactTestRunSuite.h | 51 + .../Artifact/Dynamic/TestImpactTestSuite.h | 35 + ...TestImpactBuildTargetDescriptorFactory.cpp | 172 ++ .../TestImpactBuildTargetDescriptorFactory.h | 33 + .../Factory/TestImpactChangeListFactory.cpp | 175 ++ .../Factory/TestImpactChangeListFactory.h | 29 + .../TestImpactModuleCoverageFactory.cpp | 154 ++ .../Factory/TestImpactModuleCoverageFactory.h | 26 + .../TestImpactTestEnumerationSuiteFactory.cpp | 94 + .../TestImpactTestEnumerationSuiteFactory.h | 26 + .../Factory/TestImpactTestRunSuiteFactory.cpp | 143 ++ .../Factory/TestImpactTestRunSuiteFactory.h | 26 + .../TestImpactTestTargetMetaArtifactFactory.h | 25 + .../TestImpactTestTargetMetaMapFactory.cpp | 89 + .../TestImpactTestTargetMetaMapFactory.h | 25 + .../TestImpactBuildTargetDescriptor.cpp | 22 + .../Static/TestImpactBuildTargetDescriptor.h | 55 + .../TestImpactProductionTargetDescriptor.cpp | 21 + .../TestImpactProductionTargetDescriptor.h | 25 + .../TestImpactTargetDescriptorCompiler.cpp | 45 + .../TestImpactTargetDescriptorCompiler.h | 31 + .../Static/TestImpactTestTargetDescriptor.cpp | 22 + .../Static/TestImpactTestTargetDescriptor.h | 28 + .../Static/TestImpactTestTargetMeta.h | 36 + .../Artifact/TestImpactArtifactException.h | 26 + .../Runtime/Code/Source/Dummy.cpp | 11 - .../Clang/testimpactframework_clang.cmake | 12 + .../MSVC/testimpactframework_msvc.cmake | 12 + .../Source/Platform/Windows/Dummy_Windows.cpp | 11 - .../Windows/Process/TestImpactWin32_Handle.h | 92 + .../Windows/Process/TestImpactWin32_Pipe.cpp | 67 + .../Windows/Process/TestImpactWin32_Pipe.h | 52 + .../Process/TestImpactWin32_Process.cpp | 259 +++ .../Windows/Process/TestImpactWin32_Process.h | 101 + .../TestImpactWin32_ProcessLauncher.cpp | 24 + .../Windows/platform_windows_files.cmake | 7 +- .../Process/JobRunner/TestImpactProcessJob.h | 139 ++ .../JobRunner/TestImpactProcessJobInfo.h | 73 + .../JobRunner/TestImpactProcessJobRunner.h | 190 ++ .../Scheduler/TestImpactProcessScheduler.cpp | 270 +++ .../Scheduler/TestImpactProcessScheduler.h | 110 + .../Code/Source/Process/TestImpactProcess.cpp | 32 + .../Code/Source/Process/TestImpactProcess.h | 61 + .../Process/TestImpactProcessException.h | 26 + .../Source/Process/TestImpactProcessInfo.cpp | 67 + .../Source/Process/TestImpactProcessInfo.h | 93 + .../Process/TestImpactProcessLauncher.h | 27 + .../Source/Target/TestImpactBuildTarget.cpp | 48 + .../Source/Target/TestImpactBuildTarget.h | 55 + .../Source/Target/TestImpactBuildTargetList.h | 125 ++ .../Target/TestImpactProductionTarget.cpp | 21 + .../Target/TestImpactProductionTarget.h | 31 + .../Target/TestImpactProductionTargetList.h | 22 + .../Source/Target/TestImpactTargetException.h | 25 + .../Source/Target/TestImpactTestTarget.cpp | 32 + .../Code/Source/Target/TestImpactTestTarget.h | 41 + .../Source/Target/TestImpactTestTargetList.h | 22 + .../Enumeration/TestImpactTestEnumeration.h | 22 + .../TestImpactTestEnumerationException.h | 26 + .../TestImpactTestEnumerationSerializer.cpp | 100 + .../TestImpactTestEnumerationSerializer.h | 26 + .../Enumeration/TestImpactTestEnumerator.cpp | 190 ++ .../Enumeration/TestImpactTestEnumerator.h | 94 + .../Source/Test/Job/TestImpactTestJobCommon.h | 36 + .../Test/Job/TestImpactTestJobException.h | 26 + .../Source/Test/Job/TestImpactTestJobRunner.h | 141 ++ .../Run/TestImpactInstrumentedTestRunner.cpp | 89 + .../Run/TestImpactInstrumentedTestRunner.h | 71 + .../Test/Run/TestImpactTestCoverage.cpp | 68 + .../Source/Test/Run/TestImpactTestCoverage.h | 54 + .../Source/Test/Run/TestImpactTestRun.cpp | 70 + .../Code/Source/Test/Run/TestImpactTestRun.h | 51 + .../Test/Run/TestImpactTestRunException.h | 25 + .../Test/Run/TestImpactTestRunJobData.cpp | 26 + .../Test/Run/TestImpactTestRunJobData.h | 31 + .../Test/Run/TestImpactTestRunSerializer.cpp | 186 ++ .../Test/Run/TestImpactTestRunSerializer.h | 26 + .../Source/Test/Run/TestImpactTestRunner.cpp | 58 + .../Source/Test/Run/TestImpactTestRunner.h | 46 + .../Test/TestImpactTestSuiteContainer.h | 101 + .../Code/Source/TestImpactException.cpp | 31 + .../Code/Source/TestImpactFrameworkPath.cpp | 38 + ...ImpactBuildTargetDescriptorFactoryTest.cpp | 370 ++++ .../TestImpactChangeListFactoryTest.cpp | 288 +++ .../TestImpactModuleCoverageFactoryTest.cpp | 522 +++++ ...TestImpactTargetDescriptorCompilerTest.cpp | 150 ++ ...tImpactTestEnumerationSuiteFactoryTest.cpp | 589 ++++++ .../TestImpactTestRunSuiteFactoryTest.cpp | 592 ++++++ ...TestImpactTestTargetMetaMapFactoryTest.cpp | 239 +++ .../TestImpactProcessSchedulerTest.cpp | 718 +++++++ .../Tests/Process/TestImpactProcessTest.cpp | 491 +++++ .../Target/TestImpactBuildTargetTest.cpp | 352 ++++ .../TestImpactInstrumentedTestRunnerTest.cpp | 822 ++++++++ .../Tests/Test/TestImpactTestCoverageTest.cpp | 208 ++ .../Test/TestImpactTestEnumeratorTest.cpp | 890 ++++++++ ...TestImpactTestEumerationSerializerTest.cpp | 99 + .../Test/TestImpactTestRunSerializerTest.cpp | 99 + .../Tests/Test/TestImpactTestRunnerTest.cpp | 516 +++++ .../Code/Tests/TestImpactExceptionTest.cpp | 150 ++ .../Tests/TestImpactFrameworkPathTest.cpp | 137 ++ .../Tests/TestImpactTestJobRunnerCommon.h | 159 ++ .../Runtime/Code/Tests/TestImpactTestMain.cpp | 33 + .../Code/Tests/TestImpactTestUtils.cpp | 1843 +++++++++++++++++ .../Runtime/Code/Tests/TestImpactTestUtils.h | 220 ++ .../Code/Tests/TestProcess/CMakeLists.txt | 12 + .../Tests/TestProcess/Code/CMakeLists.txt | 24 + .../Code/Source/TestImpactTestProcess.cpp | 93 + .../Code/Source/TestImpactTestProcess.h | 38 + .../Source/TestImpactTestProcessLargeText.cpp | 531 +++++ .../Source/TestImpactTestProcessLargeText.h | 19 + .../Code/Source/TestImpactTestProcessMain.cpp | 19 + ...estimpactframework_testprocess_files.cmake | 18 + .../Code/Tests/TestTargetA/CMakeLists.txt | 12 + .../Tests/TestTargetA/Code/CMakeLists.txt | 28 + .../Code/Tests/TestImpactTestTargetA.cpp | 73 + ...actframework_testtargeta_tests_files.cmake | 14 + .../Code/Tests/TestTargetB/CMakeLists.txt | 12 + .../Tests/TestTargetB/Code/CMakeLists.txt | 29 + .../Code/Tests/TestImpactTestTargetB.cpp | 78 + ...actframework_testtargetb_tests_files.cmake | 14 + .../Code/Tests/TestTargetC/CMakeLists.txt | 12 + .../Tests/TestTargetC/Code/CMakeLists.txt | 29 + .../Code/Tests/TestImpactTestTargetC.cpp | 63 + ...actframework_testtargetc_tests_files.cmake | 14 + .../Code/Tests/TestTargetD/CMakeLists.txt | 12 + .../Tests/TestTargetD/Code/CMakeLists.txt | 29 + .../Code/Tests/TestImpactTestTargetD.cpp | 188 ++ ...actframework_testtargetd_tests_files.cmake | 14 + .../testimpactframework_runtime_files.cmake | 17 +- ...timpactframework_runtime_tests_files.cmake | 22 + 139 files changed, 16391 insertions(+), 37 deletions(-) create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactBitwise.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactCallback.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactException.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactFrameworkPath.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactChangeList.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactCoverage.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactTestEnumerationSuite.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactTestRunSuite.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactTestSuite.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactBuildTargetDescriptorFactory.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactBuildTargetDescriptorFactory.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactChangeListFactory.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactChangeListFactory.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactModuleCoverageFactory.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactModuleCoverageFactory.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestEnumerationSuiteFactory.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestEnumerationSuiteFactory.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestRunSuiteFactory.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestRunSuiteFactory.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaArtifactFactory.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaMapFactory.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaMapFactory.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactBuildTargetDescriptor.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactBuildTargetDescriptor.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactProductionTargetDescriptor.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactProductionTargetDescriptor.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTargetDescriptorCompiler.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTargetDescriptorCompiler.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTestTargetDescriptor.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTestTargetDescriptor.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTestTargetMeta.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/TestImpactArtifactException.h delete mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Dummy.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Common/Clang/testimpactframework_clang.cmake create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Common/MSVC/testimpactframework_msvc.cmake delete mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Dummy_Windows.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Handle.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Pipe.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Pipe.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Process.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Process.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_ProcessLauncher.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/JobRunner/TestImpactProcessJob.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/JobRunner/TestImpactProcessJobInfo.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/JobRunner/TestImpactProcessJobRunner.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/Scheduler/TestImpactProcessScheduler.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/Scheduler/TestImpactProcessScheduler.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcess.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcess.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessException.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessInfo.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessInfo.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessLauncher.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactBuildTarget.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactBuildTarget.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactBuildTargetList.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactProductionTarget.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactProductionTarget.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactProductionTargetList.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTargetException.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTestTarget.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTestTarget.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTestTargetList.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumeration.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerationException.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerationSerializer.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerationSerializer.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerator.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerator.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Job/TestImpactTestJobCommon.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Job/TestImpactTestJobException.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Job/TestImpactTestJobRunner.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactInstrumentedTestRunner.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactInstrumentedTestRunner.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestCoverage.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestCoverage.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRun.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRun.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunException.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunJobData.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunJobData.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunSerializer.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunSerializer.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunner.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunner.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/TestImpactTestSuiteContainer.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactException.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactFrameworkPath.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactBuildTargetDescriptorFactoryTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactChangeListFactoryTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactModuleCoverageFactoryTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTargetDescriptorCompilerTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTestEnumerationSuiteFactoryTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTestRunSuiteFactoryTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTestTargetMetaMapFactoryTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Process/TestImpactProcessSchedulerTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Process/TestImpactProcessTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Target/TestImpactBuildTargetTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactInstrumentedTestRunnerTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestCoverageTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestEnumeratorTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestEumerationSerializerTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestRunSerializerTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestRunnerTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactExceptionTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactFrameworkPathTest.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestJobRunnerCommon.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestMain.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestUtils.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestUtils.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/CMakeLists.txt create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/CMakeLists.txt create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcess.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcess.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcessLargeText.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcessLargeText.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcessMain.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/testimpactframework_testprocess_files.cmake create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/CMakeLists.txt create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/Code/CMakeLists.txt create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/Code/Tests/TestImpactTestTargetA.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/Code/testimpactframework_testtargeta_tests_files.cmake create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/CMakeLists.txt create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/Code/CMakeLists.txt create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/Code/Tests/TestImpactTestTargetB.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/Code/testimpactframework_testtargetb_tests_files.cmake create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/CMakeLists.txt create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/Code/CMakeLists.txt create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/Code/Tests/TestImpactTestTargetC.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/Code/testimpactframework_testtargetc_tests_files.cmake create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/CMakeLists.txt create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/Code/CMakeLists.txt create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/Code/Tests/TestImpactTestTargetD.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/Code/testimpactframework_testtargetd_tests_files.cmake create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/testimpactframework_runtime_tests_files.cmake diff --git a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsole.cpp b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsole.cpp index c20b3c60fe..12743f4b38 100644 --- a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsole.cpp +++ b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsole.cpp @@ -1,14 +1,14 @@ /* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) { diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/CMakeLists.txt b/Code/Tools/TestImpactFramework/Runtime/Code/CMakeLists.txt index d48acd73c5..4c021a12b4 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/CMakeLists.txt +++ b/Code/Tools/TestImpactFramework/Runtime/Code/CMakeLists.txt @@ -9,8 +9,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # - ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Source/Platform/${PAL_PLATFORM_NAME}) +ly_get_list_relative_pal_filename(common_dir ${CMAKE_CURRENT_LIST_DIR}/Source/Platform/Common) + +add_subdirectory(Tests/TestProcess) +add_subdirectory(Tests/TestTargetA) +add_subdirectory(Tests/TestTargetB) +add_subdirectory(Tests/TestTargetC) +add_subdirectory(Tests/TestTargetD) ly_add_target( NAME TestImpact.Runtime.Static STATIC @@ -18,12 +24,64 @@ ly_add_target( FILES_CMAKE testimpactframework_runtime_files.cmake ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake + PLATFORM_INCLUDE_FILES + ${common_dir}/${PAL_TRAIT_COMPILER_ID}/testimpactframework_${PAL_TRAIT_COMPILER_ID_LOWERCASE}.cmake INCLUDE_DIRECTORIES PRIVATE Source PUBLIC Include BUILD_DEPENDENCIES - Public + PUBLIC AZ::AzCore -) \ No newline at end of file +) + +################################################################################ +# Tests +################################################################################ + +ly_add_target( + NAME TestImpact.Runtime.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE AZ + FILES_CMAKE + testimpactframework_runtime_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Include + Source + Tests + BUILD_DEPENDENCIES + PRIVATE + AZ::AzTestShared + AZ::AzTest + AZ::TestImpact.Runtime.Static + RUNTIME_DEPENDENCIES + AZ::AzTestRunner + AZ::TestImpact.TestProcess.Console + AZ::TestImpact.TestTargetA.Tests + AZ::TestImpact.TestTargetB.Tests + AZ::TestImpact.TestTargetC.Tests + AZ::TestImpact.TestTargetD.Tests + COMPILE_DEFINITIONS + PRIVATE + LY_TEST_IMPACT_AZ_TESTRUNNER_BIN="$" + LY_TEST_IMPACT_TEST_PROCESS_BIN="$" + LY_TEST_IMPACT_TEST_TARGET_A_BIN="$" + LY_TEST_IMPACT_TEST_TARGET_B_BIN="$" + LY_TEST_IMPACT_TEST_TARGET_C_BIN="$" + LY_TEST_IMPACT_TEST_TARGET_D_BIN="$" + LY_TEST_IMPACT_TEST_TARGET_A_BASE_NAME="$" + LY_TEST_IMPACT_TEST_TARGET_B_BASE_NAME="$" + LY_TEST_IMPACT_TEST_TARGET_C_BASE_NAME="$" + LY_TEST_IMPACT_TEST_TARGET_D_BASE_NAME="$" + LY_TEST_IMPACT_TEST_TARGET_ENUMERATION_DIR="${GTEST_XML_OUTPUT_DIR}/TestImpact/Temp/Exclusive/Enum" + LY_TEST_IMPACT_TEST_TARGET_RESULTS_DIR="${GTEST_XML_OUTPUT_DIR}/TestImpact/Temp/Exclusive/Result" + LY_TEST_IMPACT_TEST_TARGET_COVERAGE_DIR="${GTEST_XML_OUTPUT_DIR}/TestImpact/Temp/Exclusive/Coverage" + LY_TEST_IMPACT_INSTRUMENTATION_BIN="${LY_TEST_IMPACT_INSTRUMENTATION_BIN}" + LY_TEST_IMPACT_MODULES_DIR="${CMAKE_BINARY_DIR}" + LY_TEST_IMPACT_COVERAGE_SOURCES_DIR="${CMAKE_CURRENT_SOURCE_DIR}" +) + +ly_add_googletest( + NAME AZ::TestImpact.Runtime.Tests +) diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactBitwise.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactBitwise.h new file mode 100644 index 0000000000..73278f6be6 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactBitwise.h @@ -0,0 +1,35 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +namespace TestImpact +{ + //! Convinience namespace for allowing bitwise operations on enum classes. + //! @note Any types declared in this namespace will have the bitwise operator overloads declared within. + namespace Bitwise + { + template + Flags operator|(Flags lhs, Flags rhs) + { + return static_cast( + static_cast::type>(lhs) | static_cast::type>(rhs)); + } + + template + bool IsFlagSet(Flags flags, Flags flag) + { + return static_cast( + static_cast::type>(flags) & static_cast::type>(flag)); + } + } // namespace Bitwise +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactCallback.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactCallback.h new file mode 100644 index 0000000000..a8c9542b47 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactCallback.h @@ -0,0 +1,23 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +namespace TestImpact +{ + //! Generic callback result used by test impact systems. + enum class CallbackResult : bool + { + Continue, + Abort + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactException.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactException.h new file mode 100644 index 0000000000..83ba48aa46 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactException.h @@ -0,0 +1,51 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include + +//! Evaluates the specified condition and throws the specified exception with the specified +// !message upon failure. +#define AZ_TestImpact_Eval(CONDITION, EXCEPTION_TYPE, MSG) \ + do \ + { \ + static_assert( \ + AZStd::is_base_of_v, \ + "TestImpact Eval macro must only be used with TestImpact exceptions"); \ + if(!(CONDITION)) \ + { \ + throw(EXCEPTION_TYPE(MSG)); \ + } \ + } \ + while (0) + +namespace TestImpact +{ + //! Base class for test impact framework exceptions. + //! @note The message passed in to the constructor is copied and thus safe with dynamic strings. + class Exception + : public std::exception + { + public: + explicit Exception() = default; + explicit Exception(const AZStd::string& msg); + explicit Exception(const char* msg); + const char* what() const noexcept override; + + private: + //! Error message detailing the reason for the exception. + AZStd::string m_msg; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactFrameworkPath.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactFrameworkPath.h new file mode 100644 index 0000000000..affab6daa7 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactFrameworkPath.h @@ -0,0 +1,41 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! Wrapper for OS paths relative to a specified parent path. + //! @note Mimics path semantics only, makes no guarantees about validity. + class FrameworkPath + { + public: + FrameworkPath() = default; + //! Creates a path with no parent. + explicit FrameworkPath(const AZ::IO::Path& absolutePath); + //! Creates a path with an absolute path and path relative to the specified parent path. + explicit FrameworkPath(const AZ::IO::Path& absolutePath, const FrameworkPath& relativeTo); + + //! Retrieves the absolute path. + const AZ::IO::Path& Absolute() const; + //! Retrieves the path relative to the specified parent path. + const AZ::IO::Path& Relative() const; + + private: + //! The absolute path value. + AZ::IO::Path m_absolutePath; + //! The path value relative to the specified parent path. + AZ::IO::Path m_relativePath; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactChangeList.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactChangeList.h new file mode 100644 index 0000000000..6d966385ac --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactChangeList.h @@ -0,0 +1,27 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +namespace TestImpact +{ + //! Artifact produced by the unified diff parsing process representing the file CRUD operations of a given diff. + struct ChangeList + { + AZStd::vector m_createdFiles; + AZStd::vector m_updatedFiles; + AZStd::vector m_deletedFiles; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactCoverage.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactCoverage.h new file mode 100644 index 0000000000..3c5b5353a7 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactCoverage.h @@ -0,0 +1,41 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include + +namespace TestImpact +{ + //! Coverage information about a particular line. + struct LineCoverage + { + size_t m_lineNumber = 0; //!< The source line number this covers. + size_t m_hitCount = 0; //!< Number of times this line was covered (zero if not covered). + }; + + //! Coverage information about a particular source file. + struct SourceCoverage + { + AZ::IO::Path m_path; //!< Source file path. + AZStd::optional> m_coverage; //!< Source file line coverage (empty if source level coverage only). + }; + + //! Coverage information about a particular module (executable, shared library). + struct ModuleCoverage + { + AZ::IO::Path m_path; //!< Module path. + AZStd::vector m_sources; //!< Sources of this module that are covered. + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactTestEnumerationSuite.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactTestEnumerationSuite.h new file mode 100644 index 0000000000..e4c22a8787 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactTestEnumerationSuite.h @@ -0,0 +1,21 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + using TestEnumerationCase = TestCase; //!< Test case for test enumeration artifacts. + using TestEnumerationSuite = TestSuite; //!< Test suite for test enumeration artifacts. +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactTestRunSuite.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactTestRunSuite.h new file mode 100644 index 0000000000..22254db38e --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactTestRunSuite.h @@ -0,0 +1,51 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include +#include + +namespace TestImpact +{ + //! Result of a test that was ran. + enum class TestRunResult : bool + { + Failed, //! The given test failed. + Passed //! The given test passed. + }; + + //! Status of test as to whether or not it was ran. + enum class TestRunStatus : bool + { + NotRun, //!< The test was not run (typically because the test run was aborted by the client or runner before the test could run). + Run //!< The test was run (see TestRunResult for the result of this test). + }; + + //! Test case for test run artifacts. + struct TestRunCase + : public TestCase + { + AZStd::optional m_result; + AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds{0}; //! Duration this test took to run. + TestRunStatus m_status = TestRunStatus::NotRun; + }; + + //! Test suite for test run artifacts. + struct TestRunSuite + : public TestSuite + { + AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds{0}; //!< Duration this test suite took to run all of its tests. + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactTestSuite.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactTestSuite.h new file mode 100644 index 0000000000..0646dd384a --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Dynamic/TestImpactTestSuite.h @@ -0,0 +1,35 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +namespace TestImpact +{ + //! Artifact describing basic information about a test case. + struct TestCase + { + AZStd::string m_name; + bool m_enabled = false; + }; + + //! Artifact describing basic information about a test suite. + template + struct TestSuite + { + AZStd::string m_name; + bool m_enabled = false; + AZStd::vector m_tests; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactBuildTargetDescriptorFactory.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactBuildTargetDescriptorFactory.cpp new file mode 100644 index 0000000000..9e82170dff --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactBuildTargetDescriptorFactory.cpp @@ -0,0 +1,172 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include +#include + +namespace TestImpact +{ + namespace + { + // Keys for pertinent JSON node and attribute names + constexpr const char* Keys[] = + { + "target", + "name", + "output_name", + "path", + "sources", + "static", + "input", + "output" + }; + + enum + { + TargetKey, + NameKey, + OutputNameKey, + PathKey, + SourcesKey, + StaticKey, + InputKey, + OutputKey + }; + + } // namespace + + AutogenSources PairAutogenSources( + const AZStd::vector& inputSources, + const AZStd::vector& outputSources, + const AZStd::string& autogenMatcher) + { + AutogenSources autogenSources; + const auto matcherPattern = AZStd::regex(autogenMatcher); + AZStd::smatch inputMatches, outputMatches; + + // This has the potential to be optimized to O(n(n-1)/2) time complexity but to be perfectly honest it's not a serious + // bottleneck right now and easier gains would be achieved by constructing build target artifacts in parallel rather than + // trying to squeeze any more juice here as each build target is independent of one and other with no shared memory + for (const auto& input : inputSources) + { + AutogenPairs autogenPairs; + autogenPairs.m_input = input.String(); + const AZStd::string inputString = input.Stem().Native(); + if (AZStd::regex_search(inputString, inputMatches, matcherPattern)) + { + for (const auto& output : outputSources) + { + const AZStd::string outputString = output.Stem().Native(); + if (AZStd::regex_search(outputString, outputMatches, matcherPattern)) + { + // Note: [0] contains the whole match, [1] contains the first capture group + const auto& inputMatch = inputMatches[1]; + const auto& outputMatch = outputMatches[1]; + if (inputMatch == outputMatch) + { + autogenPairs.m_outputs.emplace_back(output); + } + } + } + } + + if (!autogenPairs.m_outputs.empty()) + { + autogenSources.emplace_back(AZStd::move(autogenPairs)); + } + } + + return autogenSources; + } + + BuildTargetDescriptor BuildTargetDescriptorFactory( + const AZStd::string& buildTargetData, + const AZStd::vector& staticSourceExtensionExcludes, + const AZStd::vector& autogenInputExtensionExcludes, + const AZStd::string& autogenMatcher) + { + AZ_TestImpact_Eval(!autogenMatcher.empty(), ArtifactException, "Autogen matcher cannot be empty"); + + BuildTargetDescriptor buildTargetDescriptor; + rapidjson::Document buildTarget; + + if (buildTarget.Parse(buildTargetData.c_str()).HasParseError()) + { + throw TestImpact::ArtifactException("Could not parse build target data"); + } + + const auto& target = buildTarget[Keys[TargetKey]]; + buildTargetDescriptor.m_buildMetaData.m_name = target[Keys[NameKey]].GetString(); + buildTargetDescriptor.m_buildMetaData.m_outputName = target[Keys[OutputNameKey]].GetString(); + buildTargetDescriptor.m_buildMetaData.m_path = target["path"].GetString(); + + AZ_TestImpact_Eval(!buildTargetDescriptor.m_buildMetaData.m_name.empty(), ArtifactException, "Target name cannot be empty"); + AZ_TestImpact_Eval( + !buildTargetDescriptor.m_buildMetaData.m_outputName.empty(), ArtifactException, "Target output name cannot be empty"); + AZ_TestImpact_Eval(!buildTargetDescriptor.m_buildMetaData.m_path.empty(), ArtifactException, "Target path cannot be empty"); + + const auto& sources = buildTarget[Keys[SourcesKey]]; + const auto& staticSources = sources[Keys[StaticKey]].GetArray(); + if (!staticSources.Empty()) + { + buildTargetDescriptor.m_sources.m_staticSources = AZStd::vector(); + + for (const auto& source : staticSources) + { + const AZ::IO::Path sourcePath = AZ::IO::Path(source.GetString()); + if (AZStd::find( + staticSourceExtensionExcludes.begin(), staticSourceExtensionExcludes.end(), sourcePath.Extension().Native()) == + staticSourceExtensionExcludes.end()) + { + buildTargetDescriptor.m_sources.m_staticSources.emplace_back(AZStd::move(sourcePath)); + } + } + } + + const auto& inputSources = buildTarget[Keys[SourcesKey]][Keys[InputKey]].GetArray(); + const auto& outputSources = buildTarget[Keys[SourcesKey]][Keys[OutputKey]].GetArray(); + if (!inputSources.Empty() || !outputSources.Empty()) + { + AZ_TestImpact_Eval( + !inputSources.Empty() && !outputSources.Empty(), ArtifactException, "Autogen malformed, input or output sources are empty"); + + AZStd::vector inputPaths; + AZStd::vector outputPaths; + inputPaths.reserve(inputSources.Size()); + outputPaths.reserve(outputSources.Size()); + + for (const auto& source : inputSources) + { + const AZ::IO::Path sourcePath = AZ::IO::Path(source.GetString()); + if (AZStd::find( + autogenInputExtensionExcludes.begin(), autogenInputExtensionExcludes.end(), sourcePath.Extension().Native()) == + autogenInputExtensionExcludes.end()) + { + inputPaths.emplace_back(AZStd::move(sourcePath)); + } + } + + for (const auto& source : outputSources) + { + outputPaths.emplace_back(AZStd::move(AZ::IO::Path(source.GetString()))); + } + + buildTargetDescriptor.m_sources.m_autogenSources = PairAutogenSources(inputPaths, outputPaths, autogenMatcher); + } + + return buildTargetDescriptor; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactBuildTargetDescriptorFactory.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactBuildTargetDescriptorFactory.h new file mode 100644 index 0000000000..b23e00478a --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactBuildTargetDescriptorFactory.h @@ -0,0 +1,33 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include +#include + +namespace TestImpact +{ + //! Constructs a build target artifact from the specified build target data. + //! @param buildTargetData The raw build target data in JSON format. + //! @param staticSourceExcludes The list of file extensions to exclude for static sources. + //! @param autogenInputExtentsionExcludes The list of file extensions to exclude for autogen input sources. + //! @param autogenMatcher The regex pattern used to match autogen input filenames with output filenames. + //! @return The constructed build target artifact. + BuildTargetDescriptor BuildTargetDescriptorFactory( + const AZStd::string& buildTargetData, + const AZStd::vector& staticSourceExtentsionExcludes, + const AZStd::vector& autogenInputExtentsionExcludes, + const AZStd::string& autogenMatcher); +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactChangeListFactory.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactChangeListFactory.cpp new file mode 100644 index 0000000000..4b2a33355b --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactChangeListFactory.cpp @@ -0,0 +1,175 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include + +namespace TestImpact +{ + namespace Utils + { + template + auto split(C&& str, const AZStd::string& delimiter) + { + AZStd::vector strings; + + for (auto p = str.data(), end = p + str.length(); p != end; p += ((p == end) ? 0 : delimiter.length())) + { + const auto pre = p; + p = AZStd::search(pre, end, delimiter.cbegin(), delimiter.cend()); + + if (p != pre) + { + strings.emplace_back(pre, p - pre); + } + } + + return strings; + } + } // namespace Utils + + namespace UnifiedDiff + { + class UnifiedDiffParser + { + public: + ChangeList Parse(const AZStd::string& unifiedDiff); + + private: + AZStd::optional GetTargetFile(const AZStd::string_view& targetFile); + ChangeList GenerateChangelist( + const AZStd::vector>& src, + const AZStd::vector>& dst); + + const AZStd::string m_srcFilePrefix = "--- "; + const AZStd::string m_dstFilePrefix = "+++ "; + const AZStd::string m_gitTargetPrefix = "b/"; + const AZStd::string m_perforceTargetPrefix = "/b/"; + const AZStd::string m_renameFromPrefix = "rename from "; + const AZStd::string m_renameToPrefix = "rename to "; + const AZStd::string m_nullFile = "/dev/null"; + bool m_hasGitHeader = false; + }; + + AZStd::optional UnifiedDiffParser::GetTargetFile(const AZStd::string_view& targetFile) + { + size_t startIndex = 0; + + if (targetFile.starts_with(m_renameFromPrefix)) + { + startIndex = m_renameFromPrefix.length(); + } + else if (targetFile.starts_with(m_renameToPrefix)) + { + startIndex = m_renameToPrefix.length(); + } + else if (targetFile.find(m_nullFile) != AZStd::string::npos) + { + return AZStd::nullopt; + } + else + { + startIndex = m_hasGitHeader ? m_gitTargetPrefix.size() + m_dstFilePrefix.size() + : m_perforceTargetPrefix.size() + m_dstFilePrefix.size(); + } + + const auto endIndex = targetFile.find('\t'); + + if (endIndex != AZStd::string::npos) + { + return targetFile.substr(startIndex, endIndex - startIndex); + } + + return targetFile.substr(startIndex); + } + + ChangeList UnifiedDiffParser::GenerateChangelist( + const AZStd::vector>& src, const AZStd::vector>& dst) + { + AZ_TestImpact_Eval(src.size() == dst.size(), ArtifactException, "Change list source and destination file count mismatch"); + ChangeList changelist; + + for (size_t i = 0; i < src.size(); i++) + { + if (!src[i].has_value()) + { + changelist.m_createdFiles.emplace_back(dst[i].value()); + } + else if (!dst[i].has_value()) + { + changelist.m_deletedFiles.emplace_back(src[i].value()); + } + else if (src[i] != dst[i]) + { + changelist.m_deletedFiles.emplace_back(src[i].value()); + changelist.m_createdFiles.emplace_back(dst[i].value()); + } + else + { + changelist.m_updatedFiles.emplace_back(src[i].value()); + } + } + + return changelist; + } + + ChangeList UnifiedDiffParser::Parse(const AZStd::string& unifiedDiff) + { + const AZStd::string GitHeader = "diff --git"; + const auto lines = Utils::split(unifiedDiff, "\n"); + + AZStd::vector> src; + AZStd::vector> dst; + + for (const auto& line : lines) + { + if (line.starts_with(GitHeader)) + { + m_hasGitHeader = true; + } + else if (line.starts_with(m_srcFilePrefix)) + { + src.emplace_back(GetTargetFile(line)); + } + else if (line.starts_with(m_dstFilePrefix)) + { + dst.emplace_back(GetTargetFile(line)); + } + else if (line.starts_with(m_renameFromPrefix)) + { + src.emplace_back(GetTargetFile(line)); + } + else if (line.starts_with(m_renameToPrefix)) + { + dst.emplace_back(GetTargetFile(line)); + } + } + + return GenerateChangelist(src, dst); + } + + ChangeList ChangeListFactory(const AZStd::string& unifiedDiff) + { + AZ_TestImpact_Eval(!unifiedDiff.empty(), ArtifactException, "Unified diff is empty"); + UnifiedDiffParser diff; + ChangeList changeList = diff.Parse(unifiedDiff); + AZ_TestImpact_Eval( + !changeList.m_createdFiles.empty() || + !changeList.m_updatedFiles.empty() || + !changeList.m_deletedFiles.empty(), + ArtifactException, "The unified diff contained no changes"); + return changeList; + } + } // namespace UnifiedDiff +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactChangeListFactory.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactChangeListFactory.h new file mode 100644 index 0000000000..82074a66ef --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactChangeListFactory.h @@ -0,0 +1,29 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include +#include + +namespace TestImpact +{ + namespace UnifiedDiff + { + //! Constructs a change list artifact from the specified unified diff data. + //! @param unifiedDiffData The raw change list data in unified diff format. + //! @return The constructed change list artifact. + ChangeList ChangeListFactory(const AZStd::string& unifiedDiffData); + } // namespace UnifiedDiff +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactModuleCoverageFactory.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactModuleCoverageFactory.cpp new file mode 100644 index 0000000000..54ba65c762 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactModuleCoverageFactory.cpp @@ -0,0 +1,154 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include + +namespace TestImpact +{ + namespace + { + // Keys for pertinent XML node and attribute names + constexpr const char* Keys[] = + { + "packages", + "name", + "filename", + "coverage", + "classes", + "lines", + "line", + "number", + "hits", + "sources", + "source" + }; + + enum + { + PackagesKey, + NameKey, + FileNameKey, + CoverageKey, + ClassesKey, + LinesKey, + LineKey, + NumberKey, + HitsKey, + SourcesKey, + SourceKey + }; + } // namespace + + namespace Cobertura + { + // Note: OpenCppCoverage appears to have a very liberal interpretation of the Cobertura coverage file format so consider + // this implementation to be provisional and coupled to the Windows platform and OpenCppCoverage tool + AZStd::vector ModuleCoveragesFactory(const AZStd::string& coverageData) + { + AZ_TestImpact_Eval(!coverageData.empty(), ArtifactException, "Cannot parse coverage, string is empty"); + AZStd::vector modules; + AZStd::vector rawData(coverageData.begin(), coverageData.end()); + + try + { + AZ::rapidxml::xml_document<> doc; + // Parse the XML doc with default flags + doc.parse<0>(rawData.data()); + + // Coverage + const auto coverage_node = doc.first_node(Keys[CoverageKey]); + AZ_TestImpact_Eval(coverage_node, ArtifactException, "Could not parse coverage node"); + + // Sources + const auto sources_node = coverage_node->first_node(Keys[SourcesKey]); + if (!sources_node) + { + return {}; + } + + // Source + const auto source_node = sources_node->first_node(Keys[SourceKey]); + if (!source_node) + { + return {}; + } + + // Root drive (this seems to be an unconventional use of the sources section by OpenCppCoverage) + const AZStd::string pathRoot = AZStd::string(source_node->value(), source_node->value() + source_node->value_size()) + "\\"; + + const auto packages_node = coverage_node->first_node(Keys[PackagesKey]); + if (packages_node) + { + // Modules + for (auto package_node = packages_node->first_node(); package_node; package_node = package_node->next_sibling()) + { + // Module + ModuleCoverage moduleCoverage; + moduleCoverage.m_path = AZ::IO::Path(package_node->first_attribute(Keys[NameKey])->value()); + + const auto classes_node = package_node->first_node(Keys[ClassesKey]); + if (classes_node) + { + // Sources + for (auto class_node = classes_node->first_node(); class_node; class_node = class_node->next_sibling()) + { + // Source + SourceCoverage sourceCoverage; + sourceCoverage.m_path = AZ::IO::Path(pathRoot + class_node->first_attribute(Keys[FileNameKey])->value()); + + const auto lines_node = class_node->first_node(Keys[LinesKey]); + if (lines_node) + { + AZStd::vector lineCoverage; + + // Lines + for (auto line_node = lines_node->first_node(); line_node; line_node = line_node->next_sibling()) + { + // Line + const size_t number = + AZStd::stol(AZStd::string(line_node->first_attribute(Keys[NumberKey])->value())); + const size_t hits = AZStd::stol(AZStd::string(line_node->first_attribute(Keys[HitsKey])->value())); + lineCoverage.emplace_back(LineCoverage{number, hits}); + } + + if (!lineCoverage.empty()) + { + sourceCoverage.m_coverage.emplace(AZStd::move(lineCoverage)); + } + } + + moduleCoverage.m_sources.emplace_back(AZStd::move(sourceCoverage)); + } + } + + modules.emplace_back(AZStd::move(moduleCoverage)); + } + } + } + catch (const std::exception& e) + { + AZ_Error("ModuleCoveragesFactory", false, e.what()); + throw ArtifactException(e.what()); + } + catch (...) + { + throw ArtifactException("An unknown error occurred parsing the XML data"); + } + + return modules; + } + } // namespace Cobertura +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactModuleCoverageFactory.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactModuleCoverageFactory.h new file mode 100644 index 0000000000..c238901017 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactModuleCoverageFactory.h @@ -0,0 +1,26 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + namespace Cobertura + { + //! Constructs a list of module coverage artifacts from the specified coverage data. + //! @param coverageData The raw coverage data in XML format. + //! @return The constructed list of module coverage artifacts. + AZStd::vector ModuleCoveragesFactory(const AZStd::string& coverageData); + } // namespace Cobertura +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestEnumerationSuiteFactory.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestEnumerationSuiteFactory.cpp new file mode 100644 index 0000000000..bf8a19c812 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestEnumerationSuiteFactory.cpp @@ -0,0 +1,94 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include + +namespace TestImpact +{ + namespace + { + // Keys for pertinent XML node and attribute names + constexpr const char* Keys[] = + { + "testsuites", + "testsuite", + "name", + "testcase" + }; + + enum + { + TestSuitesKey, + TestSuiteKey, + NameKey, + TestCaseKey + }; + } // namespace + + namespace GTest + { + AZStd::vector TestEnumerationSuitesFactory(const AZStd::string& testEnumerationData) + { + AZ_TestImpact_Eval(!testEnumerationData.empty(), ArtifactException, "Cannot parse enumeration, string is empty"); + AZStd::vector testSuites; + AZStd::vector rawData(testEnumerationData.begin(), testEnumerationData.end()); + + try + { + AZ::rapidxml::xml_document<> doc; + // Parse the XML doc with default flags + doc.parse<0>(rawData.data()); + + const auto testsuites_node = doc.first_node(Keys[TestSuitesKey]); + AZ_TestImpact_Eval(testsuites_node, ArtifactException, "Could not parse enumeration, XML is invalid"); + for (auto testsuite_node = testsuites_node->first_node(Keys[TestSuiteKey]); testsuite_node; + testsuite_node = testsuite_node->next_sibling()) + { + const auto isEnabled = [](const AZStd::string& name) + { + return !name.starts_with("DISABLED_") && name.find("/DISABLED_") == AZStd::string::npos; + }; + + TestEnumerationSuite testSuite; + testSuite.m_name = testsuite_node->first_attribute(Keys[NameKey])->value(); + testSuite.m_enabled = isEnabled(testSuite.m_name); + + for (auto testcase_node = testsuite_node->first_node(Keys[TestCaseKey]); testcase_node; + testcase_node = testcase_node->next_sibling()) + { + TestEnumerationCase testCase; + testCase.m_name = testcase_node->first_attribute(Keys[NameKey])->value(); + testCase.m_enabled = isEnabled(testCase.m_name); + testSuite.m_tests.emplace_back(AZStd::move(testCase)); + } + + testSuites.emplace_back(AZStd::move(testSuite)); + } + } + catch (const std::exception& e) + { + AZ_Error("TestEnumerationSuitesFactory", false, e.what()); + throw ArtifactException(e.what()); + } + catch (...) + { + throw ArtifactException("An unknown error occured parsing the XML data"); + } + + return testSuites; + } + } // namespace GTest +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestEnumerationSuiteFactory.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestEnumerationSuiteFactory.h new file mode 100644 index 0000000000..4be58b2116 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestEnumerationSuiteFactory.h @@ -0,0 +1,26 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + namespace GTest + { + //! Constructs a list of test enumeration suite artifacts from the specified test enumeraion data. + //! @param testEnumerationData The raw test enumeration data in XML format. + //! @return The constructed list of test enumeration suite artifacts. + AZStd::vector TestEnumerationSuitesFactory(const AZStd::string& testEnumerationData); + } // namespace GTest +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestRunSuiteFactory.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestRunSuiteFactory.cpp new file mode 100644 index 0000000000..506e058824 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestRunSuiteFactory.cpp @@ -0,0 +1,143 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include +#include + +namespace TestImpact +{ + namespace + { + // Keys for pertinent XML node and attribute names + constexpr const char* Keys[] = + { + "testsuites", + "testsuite", + "name", + "testcase", + "status", + "run", + "notrun", + "time" + }; + + enum + { + TestSuitesKey, + TestSuiteKey, + NameKey, + TestCaseKey, + StatusKey, + RunKey, + NotRunKey, + DurationKey + }; + } // namespace + + namespace GTest + { + AZStd::vector TestRunSuitesFactory(const AZStd::string& testEnumerationData) + { + AZ_TestImpact_Eval(!testEnumerationData.empty(), ArtifactException, "Cannot parse test run, string is empty"); + AZStd::vector testSuites; + AZStd::vector rawData(testEnumerationData.begin(), testEnumerationData.end()); + + try + { + AZ::rapidxml::xml_document<> doc; + // Parse the XML doc with default flags + doc.parse<0>(rawData.data()); + + const auto testsuites_node = doc.first_node(Keys[TestSuitesKey]); + AZ_TestImpact_Eval(testsuites_node, ArtifactException, "Could not parse enumeration, XML is invalid"); + for (auto testsuite_node = testsuites_node->first_node(Keys[TestSuiteKey]); testsuite_node; + testsuite_node = testsuite_node->next_sibling()) + { + const auto isEnabled = [](const AZStd::string& name) + { + return !name.starts_with("DISABLED_") && name.find("/DISABLED_") == AZStd::string::npos; + }; + + const auto getDuration = [](const AZ::rapidxml::xml_node<>* node) + { + const AZStd::string duration = node->first_attribute(Keys[DurationKey])->value(); + return AZStd::chrono::milliseconds(AZStd::stof(duration) * 1000.f); + }; + + TestRunSuite testSuite; + testSuite.m_name = testsuite_node->first_attribute(Keys[NameKey])->value(); + testSuite.m_enabled = isEnabled(testSuite.m_name); + testSuite.m_duration = getDuration(testsuite_node); + + for (auto testcase_node = testsuite_node->first_node(Keys[TestCaseKey]); testcase_node; + testcase_node = testcase_node->next_sibling()) + { + const auto getStatus = [](const AZ::rapidxml::xml_node<>* node) + { + const AZStd::string status = node->first_attribute(Keys[StatusKey])->value(); + if (status == Keys[RunKey]) + { + return TestRunStatus::Run; + } + else if (status == Keys[NotRunKey]) + { + return TestRunStatus::NotRun; + } + + throw ArtifactException(AZStd::string::format("Unexpected run status: %s", status.c_str())); + }; + + const auto getResult = [](const AZ::rapidxml::xml_node<>* node) + { + for (auto child_node = node->first_node("failure"); child_node; child_node = child_node->next_sibling()) + { + return TestRunResult::Failed; + } + + return TestRunResult::Passed; + }; + + TestRunCase testCase; + testCase.m_name = testcase_node->first_attribute(Keys[NameKey])->value(); + testCase.m_enabled = isEnabled(testCase.m_name); + testCase.m_duration = getDuration(testcase_node); + testCase.m_status = getStatus(testcase_node); + + if (testCase.m_status == TestRunStatus::Run) + { + testCase.m_result = getResult(testcase_node); + } + + testSuite.m_tests.emplace_back(AZStd::move(testCase)); + } + + testSuites.emplace_back(AZStd::move(testSuite)); + } + } + catch (const std::exception& e) + { + AZ_Error("TestRunSuitesFactory", false, e.what()); + throw ArtifactException(e.what()); + } + catch (...) + { + throw ArtifactException("An unknown error occurred parsing the XML data"); + } + + return testSuites; + } + } // namespace GTest +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestRunSuiteFactory.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestRunSuiteFactory.h new file mode 100644 index 0000000000..fb16c837b2 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestRunSuiteFactory.h @@ -0,0 +1,26 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + namespace GTest + { + //! Constructs a list of test run suite artifacts from the specified test run data. + //! @param testRunData The raw test run data in XML format. + //! @return The constructed list of test run suite artifacts. + AZStd::vector TestRunSuitesFactory(const AZStd::string& testRunData); + } // namespace GTest +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaArtifactFactory.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaArtifactFactory.h new file mode 100644 index 0000000000..f25af960b9 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaArtifactFactory.h @@ -0,0 +1,25 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include + +namespace TestImpact +{ + //! Constructs a list of test target meta-data artifacts from the specified master test list data. + //! @param masterTestListData The raw master test list data in JSON format. + //! @return The constructed list of test target meta-data artifacts. + TestTargetMetas TestTargetMetaMapFactory(const AZStd::string& masterTestListData); +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaMapFactory.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaMapFactory.cpp new file mode 100644 index 0000000000..9135a3c683 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaMapFactory.cpp @@ -0,0 +1,89 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include + +#include + +namespace TestImpact +{ + namespace + { + // Keys for pertinent JSON node and attribute names + constexpr const char* Keys[] = + { + "google", + "test", + "tests", + "suite", + "launch_method", + "test_runner", + "stand_alone", + "name" + }; + + enum + { + GoogleKey, + TestKey, + TestsKey, + SuiteKey, + LaunchMethodKey, + TestRunnerKey, + StandAloneKey, + NameKey + }; + } // namespace + + TestTargetMetaMap TestTargetMetaMapFactory(const AZStd::string& masterTestListData) + { + TestTargetMetaMap testMetas; + rapidjson::Document masterTestList; + + if (masterTestList.Parse(masterTestListData.c_str()).HasParseError()) + { + throw TestImpact::ArtifactException("Could not parse test meta-data file"); + } + + const auto tests = masterTestList[Keys[GoogleKey]][Keys[TestKey]][Keys[TestsKey]].GetArray(); + for (const auto& test : tests) + { + TestTargetMeta testMeta; + testMeta.m_suite = test[Keys[SuiteKey]].GetString(); + + if (const auto buildTypeString = test[Keys[LaunchMethodKey]].GetString(); strcmp(buildTypeString, Keys[TestRunnerKey]) == 0) + { + testMeta.m_launchMethod = LaunchMethod::TestRunner; + } + else if (strcmp(buildTypeString, Keys[StandAloneKey]) == 0) + { + testMeta.m_launchMethod = LaunchMethod::StandAlone; + } + else + { + throw(ArtifactException("Unexpected test build type")); + } + + AZStd::string name = test[Keys[NameKey]].GetString(); + AZ_TestImpact_Eval(!name.empty(), ArtifactException, "Test name field cannot be empty"); + testMetas.emplace(AZStd::move(name), AZStd::move(testMeta)); + } + + // If there's no tests in the repo then something is seriously wrong + AZ_TestImpact_Eval(!testMetas.empty(), ArtifactException, "No tests were found in the repository"); + + return testMetas; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaMapFactory.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaMapFactory.h new file mode 100644 index 0000000000..039a9e89e2 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaMapFactory.h @@ -0,0 +1,25 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include + +namespace TestImpact +{ + //! Constructs a list of test target meta-data artifacts from the specified master test list data. + //! @param masterTestListData The raw master test list data in JSON format. + //! @return The constructed list of test target meta-data artifacts. + TestTargetMetaMap TestTargetMetaMapFactory(const AZStd::string& masterTestListData); +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactBuildTargetDescriptor.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactBuildTargetDescriptor.cpp new file mode 100644 index 0000000000..935ae14452 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactBuildTargetDescriptor.cpp @@ -0,0 +1,22 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +namespace TestImpact +{ + BuildTargetDescriptor::BuildTargetDescriptor(BuildMetaData&& buildMetaData, TargetSources&& sources) + : m_buildMetaData(AZStd::move(buildMetaData)) + , m_sources(AZStd::move(sources)) + { + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactBuildTargetDescriptor.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactBuildTargetDescriptor.h new file mode 100644 index 0000000000..e247b663f2 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactBuildTargetDescriptor.h @@ -0,0 +1,55 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace TestImpact +{ + //! Pairing between a given autogen input source and the generated output source(s). + struct AutogenPairs + { + AZStd::string m_input; + AZStd::vector m_outputs; + }; + + using AutogenSources = AZStd::vector; + + //! Representation of a given built target's source list. + struct TargetSources + { + AZStd::vector m_staticSources; //!< Source files used to build this target (if any). + AutogenSources m_autogenSources; //!< Autogen source files (if any). + }; + + //! Representation of a given build target's basic build infotmation. + struct BuildMetaData + { + AZStd::string m_name; //!< Build target name. + AZStd::string m_outputName; //!< Output name (sans extension) of build target binary. + AZ::IO::Path m_path; //!< Path to build target location in source tree (relative to repository root). + }; + + //! Artifact produced by the build system for each build target. Contains source and output information about said targets. + struct BuildTargetDescriptor + { + BuildTargetDescriptor() = default; + BuildTargetDescriptor(BuildMetaData&& buildMetaData, TargetSources&& sources); + + BuildMetaData m_buildMetaData; + TargetSources m_sources; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactProductionTargetDescriptor.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactProductionTargetDescriptor.cpp new file mode 100644 index 0000000000..1b9cb70dab --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactProductionTargetDescriptor.cpp @@ -0,0 +1,21 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +namespace TestImpact +{ + ProductionTargetDescriptor::ProductionTargetDescriptor(BuildTargetDescriptor&& buildTargetDescriptor) + : BuildTargetDescriptor(AZStd::move(buildTargetDescriptor)) + { + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactProductionTargetDescriptor.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactProductionTargetDescriptor.h new file mode 100644 index 0000000000..e9fccbc51a --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactProductionTargetDescriptor.h @@ -0,0 +1,25 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! Artifact produced by the target artifact compiler that represents a production build target in the repository. + struct ProductionTargetDescriptor + : public BuildTargetDescriptor + { + ProductionTargetDescriptor(BuildTargetDescriptor&& buildTarget); + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTargetDescriptorCompiler.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTargetDescriptorCompiler.cpp new file mode 100644 index 0000000000..e13b2083ae --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTargetDescriptorCompiler.cpp @@ -0,0 +1,45 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include + +namespace TestImpact +{ + AZStd::tuple, AZStd::vector> CompileTargetDescriptors( + AZStd::vector&& buildTargets, TestTargetMetaMap&& testTargetMetaMap) + { + AZ_TestImpact_Eval(!buildTargets.empty(), ArtifactException, "Build target descriptor list cannot be null"); + AZ_TestImpact_Eval(!testTargetMetaMap.empty(), ArtifactException, "Test target meta map cannot be null"); + + AZStd::tuple, AZStd::vector> outputTargets; + auto& [productionTargets, testTargets] = outputTargets; + + for (auto&& buildTarget : buildTargets) + { + // If this build target has an associated test artifact then it is a test target, otherwise it is a production target + if (auto&& testTargetMeta = testTargetMetaMap.find(buildTarget.m_buildMetaData.m_name); + testTargetMeta != testTargetMetaMap.end()) + { + testTargets.emplace_back(TestTargetDescriptor(AZStd::move(buildTarget), AZStd::move(testTargetMeta->second))); + } + else + { + productionTargets.emplace_back(ProductionTargetDescriptor(AZStd::move(buildTarget))); + } + } + + return outputTargets; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTargetDescriptorCompiler.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTargetDescriptorCompiler.h new file mode 100644 index 0000000000..608fa9d072 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTargetDescriptorCompiler.h @@ -0,0 +1,31 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include + +#include +#include + +namespace TestImpact +{ + //! Compiles the production target artifacts and test target artifactss from the supplied build target artifacts and test target meta + //! map artifact. + //! @param buildTargets The list of build target artifacts to be sorted into production and test artifact types. + //! @param testTargetMetaMap The map of test target meta artifacts containing the additional meta-data about each test target. + //! @return A tuple containing the production artifacts and test artifacts. + AZStd::tuple, AZStd::vector> CompileTargetDescriptors( + AZStd::vector&& buildTargets, TestTargetMetaMap&& testTargetMetaMap); +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTestTargetDescriptor.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTestTargetDescriptor.cpp new file mode 100644 index 0000000000..f45e61c554 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTestTargetDescriptor.cpp @@ -0,0 +1,22 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +namespace TestImpact +{ + TestTargetDescriptor::TestTargetDescriptor(BuildTargetDescriptor&& buildTarget, TestTargetMeta&& testTargetMeta) + : BuildTargetDescriptor(AZStd::move(buildTarget)) + , m_testMetaData(AZStd::move(testTargetMeta)) + { + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTestTargetDescriptor.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTestTargetDescriptor.h new file mode 100644 index 0000000000..7044c963e8 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTestTargetDescriptor.h @@ -0,0 +1,28 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +namespace TestImpact +{ + //! Artifact produced by the target artifact compiler that represents a test build target in the repository. + struct TestTargetDescriptor + : public BuildTargetDescriptor + { + TestTargetDescriptor(BuildTargetDescriptor&& buildTarget, TestTargetMeta&& testTargetMeta); + + TestTargetMeta m_testMetaData; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTestTargetMeta.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTestTargetMeta.h new file mode 100644 index 0000000000..57ae2fe894 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Static/TestImpactTestTargetMeta.h @@ -0,0 +1,36 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +namespace TestImpact +{ + //! Method used to launch the test target. + enum class LaunchMethod : bool + { + TestRunner, //!< Target is launched through a separate test runner binary. + StandAlone //!< Target is launched directly by itself. + }; + + //! Artifact produced by the build system for each test target containing the additional meta-data about the test. + struct TestTargetMeta + { + AZStd::string m_suite; + LaunchMethod m_launchMethod = LaunchMethod::TestRunner; + }; + + //! Map between test target name and test target meta-data. + using TestTargetMetaMap = AZStd::unordered_map; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/TestImpactArtifactException.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/TestImpactArtifactException.h new file mode 100644 index 0000000000..5d2a4fabd2 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/TestImpactArtifactException.h @@ -0,0 +1,26 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! Exception for artifacts and artifact parsing operations. + class ArtifactException + : public Exception + { + public: + using Exception::Exception; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dummy.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dummy.cpp deleted file mode 100644 index 1ac4c2487f..0000000000 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dummy.cpp +++ /dev/null @@ -1,11 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Common/Clang/testimpactframework_clang.cmake b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Common/Clang/testimpactframework_clang.cmake new file mode 100644 index 0000000000..af1177e359 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Common/Clang/testimpactframework_clang.cmake @@ -0,0 +1,12 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(LY_COMPILE_OPTIONS PUBLIC -fexceptions) diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Common/MSVC/testimpactframework_msvc.cmake b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Common/MSVC/testimpactframework_msvc.cmake new file mode 100644 index 0000000000..79d4b190a2 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Common/MSVC/testimpactframework_msvc.cmake @@ -0,0 +1,12 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(LY_COMPILE_OPTIONS PUBLIC /EHsc) diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Dummy_Windows.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Dummy_Windows.cpp deleted file mode 100644 index 1ac4c2487f..0000000000 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Dummy_Windows.cpp +++ /dev/null @@ -1,11 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Handle.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Handle.h new file mode 100644 index 0000000000..3930732bd0 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Handle.h @@ -0,0 +1,92 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! OS function to cleanup handle + using CleanupFunc = BOOL (*)(HANDLE); + + //! RAII wrapper around OS handles. + template + class Handle + { + public: + Handle() = default; + explicit Handle(HANDLE handle); + ~Handle(); + + operator HANDLE&(); + PHANDLE operator&(); + HANDLE& operator=(HANDLE handle); + void Close(); + + private: + HANDLE m_handle = INVALID_HANDLE_VALUE; + }; + + template + Handle::Handle(HANDLE handle) + : m_handle(handle) + { + } + + template + Handle::~Handle() + { + Close(); + } + + template + Handle::operator HANDLE&() + { + return m_handle; + } + + template + PHANDLE Handle::operator&() + { + return &m_handle; + } + + template + HANDLE& Handle::operator=(HANDLE handle) + { + // Setting the handle to INVALID_HANDLE_VALUE will close the handle + if (handle == INVALID_HANDLE_VALUE && m_handle != INVALID_HANDLE_VALUE) + { + Close(); + } + else + { + m_handle = handle; + } + + return m_handle; + } + + template + void Handle::Close() + { + if (m_handle != INVALID_HANDLE_VALUE) + { + CleanupFuncT(m_handle); + m_handle = INVALID_HANDLE_VALUE; + } + } + + using ObjectHandle = Handle; + using WaitHandle = Handle; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Pipe.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Pipe.cpp new file mode 100644 index 0000000000..b872487942 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Pipe.cpp @@ -0,0 +1,67 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include "TestImpactWin32_Pipe.h" + +#include + +#include +#include + +namespace TestImpact +{ + Pipe::Pipe(SECURITY_ATTRIBUTES& sa, HANDLE& stdChannel) + { + if (!CreatePipe(&m_parent, &m_child, &sa, 0)) + { + throw ProcessException("Couldn't create pipe"); + } + + SetHandleInformation(m_parent, HANDLE_FLAG_INHERIT, 0); + stdChannel = m_child; + } + + void Pipe::ReleaseChild() + { + m_child.Close(); + } + + void Pipe::EmptyPipe() + { + DWORD bytesAvailable = 0; + while (PeekNamedPipe(m_parent, NULL, 0, NULL, &bytesAvailable, NULL) && bytesAvailable > 0) + { + // Grow the buffer by the number of bytes available in the pipe and append the new data + DWORD bytesRead; + const size_t currentSize = m_buffer.size(); + m_buffer.resize(m_buffer.size() + bytesAvailable); + if (!ReadFile(m_parent, m_buffer.data() + currentSize, bytesAvailable, &bytesRead, NULL) || bytesRead == 0) + { + throw ProcessException("Couldn't read child output from pipe"); + } + } + } + + AZStd::string Pipe::GetContentsAndClearInternalBuffer() + { + EmptyPipe(); + AZStd::string contents; + + if (m_buffer.size() > 0) + { + contents = AZStd::string(m_buffer.begin(), m_buffer.end()); + m_buffer.clear(); + } + + return contents; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Pipe.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Pipe.h new file mode 100644 index 0000000000..d7b0d616f7 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Pipe.h @@ -0,0 +1,52 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include "TestImpactWin32_Handle.h" + +#include +#include +#include + +namespace TestImpact +{ + //! RAII wrapper around OS pipes. + //! Used to connect the standard output and standard error of the child process to a sink accessible to the + //! parent process to allow the parent process to read the output(s) of the child process. + class Pipe + { + public: + Pipe(SECURITY_ATTRIBUTES& sa, HANDLE& stdChannel); + Pipe(Pipe&& other) = delete; + Pipe(Pipe& other) = delete; + Pipe& operator=(Pipe& other) = delete; + Pipe& operator=(Pipe&& other) = delete; + + //! Releases the child end of the pipe (not needed once parent has their end). + void ReleaseChild(); + + //! Empties the contents of the pipe into the internal buffer. + void EmptyPipe(); + + //! Empties the contents of the pipe into a string, clearing the internal buffer. + AZStd::string GetContentsAndClearInternalBuffer(); + + private: + // Parent and child process ends of pipe + ObjectHandle m_parent; + ObjectHandle m_child; + + // Buffer for emptying pipe upon child processes exit + AZStd::vector m_buffer; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Process.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Process.cpp new file mode 100644 index 0000000000..0a8a8643f3 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Process.cpp @@ -0,0 +1,259 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include "TestImpactWin32_Process.h" + +#include + +#include + +namespace TestImpact +{ + // Note: this is called from an OS thread + VOID ProcessWin32::ProcessExitCallback(PVOID processPtr, [[maybe_unused]] BOOLEAN EventSignalled) + { + // Lock the process destructor from being entered from the client thread + AZStd::lock_guard lifeLock(m_lifeCycleMutex); + + ProcessId id = reinterpret_cast(processPtr); + auto process = m_masterProcessList[id]; + + // Check that the process hasn't already been destructed from the client thread + if (process && process->m_isRunning) + { + // Lock state access and/or mutation from the client thread + AZStd::lock_guard stateLock(process->m_stateMutex); + process->RetrieveOSReturnCodeAndCleanUpProcess(); + } + } + + ProcessWin32::ProcessWin32(const ProcessInfo& processInfo) + : Process(processInfo) + { + AZStd::string args(m_processInfo.GetProcessPath().String()); + + if (m_processInfo.GetStartupArgs().length()) + { + args = AZStd::string::format("%s %s", args.c_str(), m_processInfo.GetStartupArgs().c_str()); + } + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = nullptr; + sa.bInheritHandle = IsPiping(); + + STARTUPINFO si; + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + + CreatePipes(sa, si); + + if (!CreateProcess( + NULL, + &args[0], + NULL, + NULL, + IsPiping(), + CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW, + NULL, NULL, + &si, &pi)) + { + throw ProcessException("Couldn't create process"); + } + + ReleaseChildPipes(); + + m_process = pi.hProcess; + m_thread = pi.hThread; + m_isRunning = true; + + { + // Lock reading of the master process list from the OS thread + AZStd::lock_guard lock(m_lifeCycleMutex); + + // Register this process with a unique id in the master process list + m_uniqueId = m_uniqueIdCounter++; + m_masterProcessList[m_uniqueId] = this; + } + + // Register the process exit signal callback + if (!RegisterWaitForSingleObject( + &m_waitCallback, + pi.hProcess, + ProcessExitCallback, + reinterpret_cast(m_uniqueId), + INFINITE, + WT_EXECUTEONLYONCE)) + { + throw ProcessException("Couldn't register wait object for process exit event"); + } + } + + bool ProcessWin32::IsPiping() const + { + return m_processInfo.ParentHasStdOutput() || m_processInfo.ParentHasStdError(); + } + + void ProcessWin32::CreatePipes(SECURITY_ATTRIBUTES& sa, STARTUPINFO& si) + { + if (IsPiping()) + { + si.dwFlags = STARTF_USESTDHANDLES; + + if (m_processInfo.ParentHasStdOutput()) + { + m_stdOutPipe.emplace(sa, si.hStdOutput); + } + + if (m_processInfo.ParentHasStdError()) + { + m_stdErrPipe.emplace(sa, si.hStdError); + } + } + } + + void ProcessWin32::ReleaseChildPipes() + { + if (m_stdOutPipe) + { + m_stdOutPipe->ReleaseChild(); + } + + if (m_stdErrPipe) + { + m_stdErrPipe->ReleaseChild(); + } + } + + void ProcessWin32::EmptyPipes() + { + if (m_stdOutPipe) + { + m_stdOutPipe->EmptyPipe(); + } + + if (m_stdErrPipe) + { + m_stdErrPipe->EmptyPipe(); + } + } + + AZStd::optional ProcessWin32::ConsumeStdOut() + { + if (m_stdOutPipe) + { + AZStd::string contents = m_stdOutPipe->GetContentsAndClearInternalBuffer(); + if (!contents.empty()) + { + return contents; + } + } + + return AZStd::nullopt; + } + + AZStd::optional ProcessWin32::ConsumeStdErr() + { + if (m_stdErrPipe) + { + AZStd::string contents = m_stdErrPipe->GetContentsAndClearInternalBuffer(); + if (!contents.empty()) + { + return contents; + } + } + + return AZStd::nullopt; + } + + void ProcessWin32::Terminate(ReturnCode returnCode) + { + // Lock process cleanup from the OS thread + AZStd::lock_guard lock(m_stateMutex); + + if (m_isRunning) + { + // Cancel the callback so we can wait for the signal ourselves + // Note: we keep the state mutex locked as closing the callback is not guaranteed to be instantaneous + m_waitCallback.Close(); + + // Terminate the process and set the error code to the terminate code + TerminateProcess(m_process, returnCode); + SetReturnCodeAndCleanUpProcesses(returnCode); + } + } + + bool ProcessWin32::IsRunning() const + { + return m_isRunning; + } + + void ProcessWin32::BlockUntilExit() + { + // Lock process cleanup from the OS thread + AZStd::lock_guard lock(m_stateMutex); + + if (m_isRunning) + { + // Cancel the callback so we can wait for the signal ourselves + // Note: we keep the state mutex locked as closing the callback is not guaranteed to be instantaneous + m_waitCallback.Close(); + + if (IsPiping()) + { + // This process will be blocked from exiting if pipe not emptied so will deadlock if we wait + // indefintely whilst there is still output in the pipes so instead keep waiting and checking + // if the pipes need emptying until the process exits + while (WAIT_OBJECT_0 != WaitForSingleObject(m_process, 1)) + { + EmptyPipes(); + } + } + else + { + // No possibility of pipe deadlocking, safe to wait indefinitely for process exit + WaitForSingleObject(m_process, INFINITE); + } + + // Now that the this process has definately exited we are safe to clean up + RetrieveOSReturnCodeAndCleanUpProcess(); + } + } + + void ProcessWin32::RetrieveOSReturnCodeAndCleanUpProcess() + { + DWORD returnCode; + GetExitCodeProcess(m_process, &returnCode); + SetReturnCodeAndCleanUpProcesses(returnCode); + } + + void ProcessWin32::SetReturnCodeAndCleanUpProcesses(ReturnCode returnCode) + { + m_returnCode = returnCode; + m_process.Close(); + m_thread.Close(); + m_waitCallback.Close(); + m_isRunning = false; + } + + ProcessWin32::~ProcessWin32() + { + // Lock the process exit signal callback from being entered OS thread + AZStd::lock_guard lock(m_lifeCycleMutex); + + // Remove this process from the master list so the process exit signal doesn't attempt to cleanup + // this process if it is deleted client side + m_masterProcessList[m_uniqueId] = nullptr; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Process.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Process.h new file mode 100644 index 0000000000..a29fecada1 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_Process.h @@ -0,0 +1,101 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include "TestImpactWin32_Handle.h" +#include "TestImpactWin32_Pipe.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace TestImpact +{ + //! Platform-specific implementation of Process. + class ProcessWin32 + : public Process + { + public: + explicit ProcessWin32(const ProcessInfo& processInfo); + ProcessWin32(ProcessWin32&& other) = delete; + ProcessWin32(ProcessWin32& other) = delete; + ProcessWin32& operator=(ProcessWin32& other) = delete; + ProcessWin32& operator=(ProcessWin32&& other) = delete; + ~ProcessWin32(); + + // Process overrides... + void Terminate(ReturnCode returnCode) override; + void BlockUntilExit() override; + bool IsRunning() const override; + AZStd::optional ConsumeStdOut() override; + AZStd::optional ConsumeStdErr() override; + + private: + //! Callback for process exit signal. + static VOID ProcessExitCallback(PVOID processPtr, BOOLEAN EventSignalled); + + //! Retrieves the return code and cleans up the OS handles. + void RetrieveOSReturnCodeAndCleanUpProcess(); + + //! Sets the return code and cleans up the OS handles + void SetReturnCodeAndCleanUpProcesses(ReturnCode returnCode); + + //! Returns true if either stdout or stderr is beign redirected. + bool IsPiping() const; + + //! Creates the parent and child pipes for stdout and/or stderr. + void CreatePipes(SECURITY_ATTRIBUTES& sa, STARTUPINFO& si); + + //! Empties all pipes so the process can exit without deadlocking. + void EmptyPipes(); + + //! Releases the child end of the stdout and/or stderr pipes/ + void ReleaseChildPipes(); + + // Flag to determine whether or not the process is in flight + AZStd::atomic_bool m_isRunning = false; + + // Unique id assigned to this process (not the same as the id assigned by the client in the ProcessInfo class) + // as used in the master process list + size_t m_uniqueId = 0; + + // Handles to OS process + ObjectHandle m_process; + ObjectHandle m_thread; + + // Handle to process exit signal callback + WaitHandle m_waitCallback; + + // Process to parent standard output piping + AZStd::optional m_stdOutPipe; + AZStd::optional m_stdErrPipe; + + // Mutex protecting process state access/mutation from the OS thread and client thread + mutable AZStd::mutex m_stateMutex; + + // Mutex keeping the process life cycles in sync between the OS thread and client thread + inline static AZStd::mutex m_lifeCycleMutex; + + // Unique counter to give each launched process a unique id + inline static size_t m_uniqueIdCounter = 1; + + // Master process list used to ensure consistency of process lifecycles between OS thread and client thread + inline static std::unordered_map m_masterProcessList; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_ProcessLauncher.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_ProcessLauncher.cpp new file mode 100644 index 0000000000..d798e4ff7b --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/Process/TestImpactWin32_ProcessLauncher.cpp @@ -0,0 +1,24 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include "TestImpactWin32_Process.h" + +#include +#include + +namespace TestImpact +{ + AZStd::unique_ptr LaunchProcess(const ProcessInfo& processInfo) + { + return AZStd::make_unique(processInfo); + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/platform_windows_files.cmake b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/platform_windows_files.cmake index 9b83a02475..32f996cdf8 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/platform_windows_files.cmake +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Platform/Windows/platform_windows_files.cmake @@ -10,5 +10,10 @@ # set(FILES - Dummy_Windows.cpp + Process/TestImpactWin32_ProcessLauncher.cpp + Process/TestImpactWin32_Process.cpp + Process/TestImpactWin32_Process.h + Process/TestImpactWin32_Handle.h + Process/TestImpactWin32_Pipe.cpp + Process/TestImpactWin32_Pipe.h ) diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/JobRunner/TestImpactProcessJob.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/JobRunner/TestImpactProcessJob.h new file mode 100644 index 0000000000..d9dd4870dd --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/JobRunner/TestImpactProcessJob.h @@ -0,0 +1,139 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +#include +#include + +namespace TestImpact +{ + //! Result of a job that was run. + enum class JobResult + { + NotExecuted, //!< The job was not executed (e.g. the job runner terminated before the job could be executed). + FailedToExecute, //!< The job failed to execute (e.g. due to the arguments used to execute the job being invalid). + Terminated, //!< The job was terminated by the job runner (e.g. job or runner timeout exceeded while job was in-flight). + ExecutedWithFailure, //!< The job was executed but exited in an erroneous state (the underlying process returned non-zero). + ExecutedWithSuccess //!< The job was executed and exited in a successful state (the underlying processes returned zero). + }; + + //! The meta-data for a given job. + struct JobMeta + { + JobResult m_result = JobResult::NotExecuted; + AZStd::optional + m_startTime; //!< The time, relative to the job runner start, that this job started. + AZStd::optional m_duration; //!< The duration that this job took to complete. + AZStd::optional m_returnCode; //!< The return code of the underlying processes of this job. + }; + + //! Representation of a unit of work to be performed by a process. + //! @tparam JobInfoT The JobInfo structure containing the information required to run this job. + //! @tparam JobPayloadT The resulting output of the processed artifact produced by this job. + template + class Job + { + public: + using Info = JobInfoT; + using Payload = JobPayloadT; + + //! Constructor with r-values for the specific use case of the job runner. + Job(Info jobInfo, JobMeta&& jobMeta, AZStd::optional&& payload); + + //! Returns the job info associated with this job. + const Info& GetJobInfo() const; + + //! Returns the result of this job. + JobResult GetResult() const; + + //! Returns the start time, relative to the job runner start, that this job started. + AZStd::chrono::high_resolution_clock::time_point GetStartTime() const; + + //! Returns the end time, relative to the job runner start, that this job ended. + AZStd::chrono::high_resolution_clock::time_point GetEndTime() const; + + //! Returns the duration that this job took to complete. + AZStd::chrono::milliseconds GetDuration() const; + + //! Returns the return code of the underlying processes of this job. + AZStd::optional GetReturnCode() const; + + //! Returns the payload produced by this job. + const AZStd::optional& GetPayload() const; + + private: + Info m_jobInfo; + JobMeta m_meta; + AZStd::optional m_payload; + }; + + template + Job::Job(Info jobInfo, JobMeta&& jobMeta, AZStd::optional&& payload) + : m_jobInfo(jobInfo) + , m_meta(AZStd::move(jobMeta)) + , m_payload(AZStd::move(payload)) + { + } + + template + const JobInfoT& Job::GetJobInfo() const + { + return m_jobInfo; + } + + template + JobResult Job::GetResult() const + { + return m_meta.m_result; + } + + template + AZStd::optional Job::GetReturnCode() const + { + return m_meta.m_returnCode; + } + + template + AZStd::chrono::high_resolution_clock::time_point Job::GetStartTime() const + { + return m_meta.m_startTime.value_or(AZStd::chrono::high_resolution_clock::time_point()); + } + + template + AZStd::chrono::high_resolution_clock::time_point Job::GetEndTime() const + { + if (m_meta.m_startTime.has_value() && m_meta.m_duration.has_value()) + { + return m_meta.m_startTime.value() + m_meta.m_duration.value(); + } + else + { + return AZStd::chrono::high_resolution_clock::time_point(); + } + } + + template + AZStd::chrono::milliseconds Job::GetDuration() const + { + return m_meta.m_duration.value_or(AZStd::chrono::milliseconds{0}); + } + + template + const AZStd::optional& Job::GetPayload() const + { + return m_payload; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/JobRunner/TestImpactProcessJobInfo.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/JobRunner/TestImpactProcessJobInfo.h new file mode 100644 index 0000000000..fd9d76652a --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/JobRunner/TestImpactProcessJobInfo.h @@ -0,0 +1,73 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! Per-job information to configure and run jobs and process the resulting artifacts. + //! @tparam AdditionalInfo Additional information to be provided to each job to be consumed by client. + template + class JobInfo + : public AdditionalInfo + { + public: + using IdType = size_t; + + //! Client-provided identifier to distinguish between different jobs. + //! @note Ids of different job types are not interchangeable. + struct Id + { + IdType m_value; + }; + + //! Constructs the job information with any additional information required by the job. + //! @param jobId The client-provided unique identifier for the job. + //! @param args The arguments used to launch the process running the job. + //! @param additionalInfo The arguments to be provided to the additional information data structure. + template + JobInfo(Id jobId, const AZStd::string& args, AdditionalInfoArgs&&... additionalInfo); + + //! Returns the id of this job. + Id GetId() const; + + //! Returns the command arguments used to execute this job. + const AZStd::string& GetArgs() const; + + private: + Id m_id; + AZStd::string m_args; + }; + + template + template + JobInfo::JobInfo(Id jobId, const AZStd::string& args, AdditionalInfoArgs&&... additionalInfo) + : AdditionalInfo{std::forward(additionalInfo)...} + , m_id(jobId) + , m_args(args) + { + } + + template + typename JobInfo::Id JobInfo::GetId() const + { + return m_id; + } + + template + const AZStd::string& JobInfo::GetArgs() const + { + return m_args; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/JobRunner/TestImpactProcessJobRunner.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/JobRunner/TestImpactProcessJobRunner.h new file mode 100644 index 0000000000..8b9e4fa69e --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/JobRunner/TestImpactProcessJobRunner.h @@ -0,0 +1,190 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace TestImpact +{ + //! Callback for job completion/failure. + //! @param jobInfo The job information associated with this job. + //! @param meta The meta-data about the job run. + //! @param std The standard output and standard error of the process running the job. + template + using JobCallback = AZStd::function; + + //! The payloads produced by the job-specific payload producer in the form of a map associating each job id with the job's payload. + template + using PayloadMap = AZStd::unordered_map>; + + //! The map used by the client to associate the job information and meta-data with the job ids. + template + using JobDataMap = AZStd::unordered_map>; + + //! The callback for producing the payloads for the jobs after all jobs have finished executing. + //! @param jobInfos The information for each job run. + //! @param jobDataMap The job data (in the form of job info and meta-data) for each job run. + template + using PayloadMapProducer = AZStd::function(const JobDataMap& jobDataMap)>; + + //! Generic job runner that launches a process for each job, records metrics about each job run and hands the payload artifacts + //! produced by each job to the client before compositing the metrics and payload artifacts for each job into a single interface + //! to be consumed by the client. + template + class JobRunner + { + public: + //! Constructs the job runner with the specified parameters to constrain job runs. + //! @param stdOutRouting The standard output routing to be specified for all jobs. + //! @param stdErrRouting The standard error routing to be specified for all jobs. + //! @param maxConcurrentProcesses he maximum number of concurrent jobs in-flight. + //! @param processTimeout The maximum duration a job may be in-flight before being forcefully terminated (nullopt if no timeout). + //! @param scheduleTimeout The maximum duration the scheduler may run before forcefully terminating all in-flight jobs (nullopt if + //! no timeout). + JobRunner( + StdOutputRouting stdOutRouting, + StdErrorRouting stdErrRouting, + size_t maxConcurrentProcesses, + AZStd::optional processTimeout, + AZStd::optional scheduleTimeout); + + //! Executes the specified jobs and returns the products of their labor. + //! @note: the job and payload callbacks are specified here rather than in the constructor to allow clients to use capturing lambdas + //! should they desire to. + //! @param jobs The arguments (and other pertinent information) required for each job to be run. + //! @param jobCallback The client callback to be called when each job changes state. + //! @param payloadMapProducer The client callback to be called when all jobs have finished to transform the work produced by each + //! job into the desired output. + AZStd::vector Execute( + const AZStd::vector& jobs, JobCallback jobCallback, + PayloadMapProducer payloadMapProducer); + + private: + size_t m_maxConcurrentProcesses = 0; //!< Maximum number of concurrent jobs being executed at a given time. + StdOutputRouting m_stdOutRouting; //!< Standard output routing from each job process to job runner. + StdErrorRouting m_stdErrRouting; //!< Standard error routing from each job process to job runner + AZStd::optional m_jobTimeout; //!< Maximum time a job can run for before being forcefully terminated. + AZStd::optional m_runnerTimeout; //!< Maximum time the job runner can run before forcefully terminating all in-flight jobs and shutting down. + }; + + template + JobRunner::JobRunner( + StdOutputRouting stdOutRouting, + StdErrorRouting stdErrRouting, + size_t maxConcurrentProcesses, + AZStd::optional jobTimeout, + AZStd::optional runnerTimeout) + : m_maxConcurrentProcesses(maxConcurrentProcesses) + , m_stdOutRouting(stdOutRouting) + , m_stdErrRouting(stdErrRouting) + , m_jobTimeout(jobTimeout) + , m_runnerTimeout(runnerTimeout) + { + } + + template + AZStd::vector JobRunner::Execute( + const AZStd::vector& jobInfos, + JobCallback jobCallback, + PayloadMapProducer payloadMapProducer) + { + AZStd::vector processes; + AZStd::unordered_map> metas; + AZStd::vector jobs; + jobs.reserve(jobInfos.size()); + processes.reserve(jobInfos.size()); + + // Transform the job infos into the underlying process infos required for each job + for (size_t jobIndex = 0; jobIndex < jobInfos.size(); jobIndex++) + { + const auto* jobInfo = &jobInfos[jobIndex]; + const auto jobId = jobInfo->GetId().m_value; + metas.emplace(jobId, AZStd::pair{JobMeta{}, jobInfo}); + processes.emplace_back(jobId, m_stdOutRouting, m_stdErrRouting, jobInfo->GetArgs()); + } + + // Wrapper around low-level process launch callback to gather job meta-data and present a simplified callback interface to the client + const auto processLaunchCallback = [&jobCallback, &jobInfos, &metas]( + TestImpact::ProcessId pid, + TestImpact::LaunchResult launchResult, + AZStd::chrono::high_resolution_clock::time_point createTime) + { + auto& [meta, jobInfo] = metas.at(pid); + if (launchResult == LaunchResult::Failure) + { + meta.m_result = JobResult::FailedToExecute; + return jobCallback(*jobInfo, meta, {}); + } + else + { + meta.m_startTime = createTime; + return CallbackResult::Continue; + } + }; + + // Wrapper around low-level process exit callback to gather job meta-data and present a simplified callback interface to the client + const auto processExitCallback = [&jobCallback, &jobInfos, &metas]( + TestImpact::ProcessId pid, + TestImpact::ExitCondition exitCondition, + TestImpact::ReturnCode returnCode, + TestImpact::StdContent&& std, + AZStd::chrono::high_resolution_clock::time_point exitTime) + { + auto& [meta, jobInfo] = metas.at(pid); + meta.m_returnCode = returnCode; + meta.m_duration = AZStd::chrono::duration_cast(exitTime - *meta.m_startTime); + if (exitCondition == ExitCondition::Gracefull && returnCode == 0) + { + meta.m_result = JobResult::ExecutedWithSuccess; + } + else if (exitCondition == ExitCondition::Terminated || exitCondition == ExitCondition::Timeout) + { + meta.m_result = JobResult::Terminated; + } + else + { + meta.m_result = JobResult::ExecutedWithFailure; + } + + return jobCallback(*jobInfo, meta, AZStd::move(std)); + }; + + // Schedule all jobs for execution + ProcessScheduler scheduler( + processes, + processLaunchCallback, + processExitCallback, + m_maxConcurrentProcesses, + m_jobTimeout, + m_runnerTimeout + ); + + // Hand off the jobs to the client for payload generation + auto payloadMap = payloadMapProducer(metas); + + // Unpack the payload map produced by the client into a vector of jobs containing the job data and payload for each job + for (const auto& jobInfo : jobInfos) + { + const auto jobId = jobInfo.GetId().m_value; + jobs.emplace_back(JobT(jobInfo, AZStd::move(metas.at(jobId).first), AZStd::move(payloadMap[jobId]))); + } + + return jobs; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/Scheduler/TestImpactProcessScheduler.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/Scheduler/TestImpactProcessScheduler.cpp new file mode 100644 index 0000000000..15230b1a46 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/Scheduler/TestImpactProcessScheduler.cpp @@ -0,0 +1,270 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include +#include + +namespace TestImpact +{ + struct ProcessScheduler::ProcessInFlight + { + AZStd::unique_ptr m_process; + AZStd::optional m_startTime; + AZStd::string m_stdOutput; + AZStd::string m_stdError; + }; + + ProcessScheduler::ProcessScheduler( + const AZStd::vector& processes, + const ProcessLaunchCallback& processLaunchCallback, + const ProcessExitCallback& processExitCallback, + size_t maxConcurrentProcesses, + AZStd::optional processTimeout, + AZStd::optional scheduleTimeout) + : m_processCreateCallback(processLaunchCallback) + , m_processExitCallback(processExitCallback) + , m_processTimeout(processTimeout) + , m_scheduleTimeout(scheduleTimeout) + , m_startTime(AZStd::chrono::high_resolution_clock::now()) + { + AZ_TestImpact_Eval(maxConcurrentProcesses != 0, ProcessException, "Max Number of concurrent processes in flight cannot be 0"); + AZ_TestImpact_Eval(!processes.empty(), ProcessException, "Number of processes to launch cannot be 0"); + AZ_TestImpact_Eval( + !m_processTimeout.has_value() || m_processTimeout->count() > 0, ProcessException, + "Process timeout must be empty or non-zero value"); + AZ_TestImpact_Eval( + !m_scheduleTimeout.has_value() || m_scheduleTimeout->count() > 0, ProcessException, + "Scheduler timeout must be empty or non-zero value"); + + const size_t numConcurrentProcesses = AZStd::min(processes.size(), maxConcurrentProcesses); + m_processPool.resize(numConcurrentProcesses); + + for (const auto& process : processes) + { + m_processQueue.emplace(process); + } + + for (auto& process : m_processPool) + { + if (PopAndLaunch(process) == CallbackResult::Abort) + { + TerminateAllProcesses(ExitCondition::Terminated); + return; + } + } + + MonitorProcesses(); + } + + ProcessScheduler::~ProcessScheduler() + { + TerminateAllProcesses(ExitCondition::Terminated); + } + + void ProcessScheduler::MonitorProcesses() + { + while (true) + { + // Check to see whether or not the scheduling has exceeded its specified runtime + if (m_scheduleTimeout.has_value()) + { + const auto shedulerRunTime = AZStd::chrono::milliseconds(AZStd::chrono::high_resolution_clock::now() - m_startTime); + + if (shedulerRunTime > m_scheduleTimeout) + { + // Runtime exceeded, terminate all proccesses and schedule no further + TerminateAllProcesses(ExitCondition::Timeout); + return; + } + } + + // Flag to determine whether or not there are currently any processes in-flight + bool processesInFlight = false; + + // Loop round the process pool and visit round robin queued up processes for launch + for (auto& processInFlight : m_processPool) + { + if (processInFlight.m_process) + { + // Process is alive (note: not necessarilly currently running) + AccumulateProcessStdContent(processInFlight); + const ProcessId processId = processInFlight.m_process->GetProcessInfo().GetId(); + + if (!processInFlight.m_process->IsRunning()) + { + // Process has exited of its own accord + const ReturnCode returnCode = processInFlight.m_process->GetReturnCode().value(); + processInFlight.m_process.reset(); + const auto exitTime = AZStd::chrono::high_resolution_clock::now(); + + // Inform the client that the processes has exited + if (CallbackResult::Abort == m_processExitCallback( + processId, + ExitCondition::Gracefull, + returnCode, + ConsumeProcessStdContent(processInFlight), + exitTime)) + { + // Client chose to abort the scheduler + TerminateAllProcesses(ExitCondition::Terminated); + return; + } + else if (!m_processQueue.empty()) + { + // This slot in the pool is free so launch one of the processes waiting in the queue + if (PopAndLaunch(processInFlight) == CallbackResult::Abort) + { + // Client chose to abort the scheduler + TerminateAllProcesses(ExitCondition::Terminated); + return; + } + else + { + // We know from the above PopAndLaunch there is at least one process in-flight this iteration + processesInFlight = true; + } + } + } + else + { + // Process is still in-flight + const auto exitTime = AZStd::chrono::high_resolution_clock::now(); + const auto runTime = AZStd::chrono::milliseconds(exitTime - processInFlight.m_startTime.value()); + + // Check to see whether or not the processes has exceeded its specified flight time + if (m_processTimeout.has_value() && runTime > m_processTimeout) + { + processInFlight.m_process->Terminate(ProcessTimeoutErrorCode); + const ReturnCode returnCode = processInFlight.m_process->GetReturnCode().value(); + processInFlight.m_process.reset(); + + if (CallbackResult::Abort == m_processExitCallback( + processId, + ExitCondition::Timeout, + returnCode, + ConsumeProcessStdContent(processInFlight), + exitTime)) + { + // Flight time exceeded, terminate this process + TerminateAllProcesses(ExitCondition::Terminated); + return; + } + } + + // We know that at least this process is in-flight this iteration + processesInFlight = true; + } + } + else + { + // Queue is empty, no more processes to launch + if (!m_processQueue.empty()) + { + if (PopAndLaunch(processInFlight) == CallbackResult::Abort) + { + // Client chose to abort the scheduler + TerminateAllProcesses(ExitCondition::Terminated); + return; + } + else + { + // We know from the above PopAndLaunch there is at least one process in-flight this iteration + processesInFlight = true; + } + } + } + } + + if (!processesInFlight) + { + break; + } + } + } + + CallbackResult ProcessScheduler::PopAndLaunch(ProcessInFlight& processInFlight) + { + auto processInfo = m_processQueue.front(); + m_processQueue.pop(); + const auto createTime = AZStd::chrono::high_resolution_clock::now(); + LaunchResult createResult = LaunchResult::Success; + + try + { + processInFlight.m_process = LaunchProcess(AZStd::move(processInfo)); + processInFlight.m_startTime = createTime; + } + catch (ProcessException& e) + { + AZ_Warning("ProcessScheduler", false, e.what()); + createResult = LaunchResult::Failure; + } + + return m_processCreateCallback(processInfo.GetId(), createResult, createTime); + } + + void ProcessScheduler::AccumulateProcessStdContent(ProcessInFlight& processInFlight) + { + // Accumulate the stdout/stderr so we don't deadlock with the process waiting for the pipe to empty before finishing + processInFlight.m_stdOutput += processInFlight.m_process->ConsumeStdOut().value_or(""); + processInFlight.m_stdError += processInFlight.m_process->ConsumeStdErr().value_or(""); + } + + StdContent ProcessScheduler::ConsumeProcessStdContent(ProcessInFlight& processInFlight) + { + return + { + !processInFlight.m_stdOutput.empty() + ? AZStd::optional{AZStd::move(processInFlight.m_stdOutput)} + : AZStd::nullopt, + !processInFlight.m_stdError.empty() + ? AZStd::optional{AZStd::move(processInFlight.m_stdError)} + : AZStd::nullopt + }; + } + + void ProcessScheduler::TerminateAllProcesses(ExitCondition exitStatus) + { + bool isCallingBackToClient = true; + const ReturnCode returnCode = static_cast(exitStatus); + + for (auto& processInFlight : m_processPool) + { + if (processInFlight.m_process) + { + processInFlight.m_process->Terminate(ProcessTerminateErrorCode); + AccumulateProcessStdContent(processInFlight); + const ProcessId processId = processInFlight.m_process->GetProcessInfo().GetId(); + + if (isCallingBackToClient) + { + const auto exitTime = AZStd::chrono::high_resolution_clock::now(); + if (CallbackResult::Abort == m_processExitCallback( + processInFlight.m_process->GetProcessInfo().GetId(), + exitStatus, + returnCode, + ConsumeProcessStdContent(processInFlight), + exitTime)) + { + // Client chose to abort the scheduler, do not make any further callbacks + isCallingBackToClient = false; + } + } + + processInFlight.m_process.reset(); + } + } + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/Scheduler/TestImpactProcessScheduler.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/Scheduler/TestImpactProcessScheduler.h new file mode 100644 index 0000000000..5531c78397 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/Scheduler/TestImpactProcessScheduler.h @@ -0,0 +1,110 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace TestImpact +{ + //! Result of the attempt to launch a process. + enum class LaunchResult : bool + { + Failure, + Success + }; + + //! The condition under which the processes exited. + //! @note For convinience, the terminate and timeout condition values are set to the corresponding return value sent to the + //! process. + enum class ExitCondition : ReturnCode + { + Gracefull, //!< Process has exited of its own accord. + Terminated = ProcessTerminateErrorCode, //!< The process was terminated by the client/scheduler. + Timeout = ProcessTimeoutErrorCode //!< The process was terminated by the scheduler due to exceeding runtime limit. + }; + + //! Callback for process launch attempt. + //! @param processId The id of the process that attempted to launch. + //! @param launchResult The result of the process launch attempt. + //! @param createTime The timestamp of the process launch attempt. + using ProcessLaunchCallback = + AZStd::function; + + //! Callback for process exit of successfully launched process. + //! @param processId The id of the process that attempted to launch. + //! @param exitStatus The circumstances upon which the processes exited. + //! @param returnCode The return code of the exited process. + //! @param std The standard output and standard error of the process. + //! @param createTime The timestamp of the process exit. + using ProcessExitCallback = + AZStd::function; + + //! Schedules a batch of processes for launch using a round robin approach to distribute the in-flight processes over + //! the specified number of concurrent process slots. + class ProcessScheduler + { + public: + //! Constructs the scheduler with the specified batch of processes. + //! @param processes The batch of processes to schedule. + //! @param processLaunchCallback The process launch callback function. + //! @param processExitCallback The process exit callback function. + //! @param maxConcurrentProcesses The maximum number of concurrent processes in-flight. + //! @param processTimeout The maximum duration a process may be in-flight for before being forcefully terminated. + //! @param scheduleTimeout The maximum duration the scheduler may run before forcefully terminating all in-flight processes. + //! processes and abandoning any queued processes. + ProcessScheduler( + const AZStd::vector& processes, + const ProcessLaunchCallback& processLaunchCallback, + const ProcessExitCallback& processExitCallback, + size_t maxConcurrentProcesses, + AZStd::optional processTimeout, + AZStd::optional scheduleTimeout); + + ~ProcessScheduler(); + + private: + struct ProcessInFlight; + + void MonitorProcesses(); + CallbackResult PopAndLaunch(ProcessInFlight& processInFlight); + void TerminateAllProcesses(ExitCondition exitStatus); + StdContent ConsumeProcessStdContent(ProcessInFlight& processInFlight); + void AccumulateProcessStdContent(ProcessInFlight& processInFlight); + + const ProcessLaunchCallback m_processCreateCallback; + const ProcessExitCallback m_processExitCallback; + const AZStd::optional m_processTimeout; + const AZStd::optional m_scheduleTimeout; + const AZStd::chrono::high_resolution_clock::time_point m_startTime; + AZStd::vector m_processPool; + AZStd::queue m_processQueue; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcess.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcess.cpp new file mode 100644 index 0000000000..3edc5af28f --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcess.cpp @@ -0,0 +1,32 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +namespace TestImpact +{ + Process::Process(const ProcessInfo& processInfo) + : m_processInfo(processInfo) + { + } + + const ProcessInfo& Process::GetProcessInfo() const + { + return m_processInfo; + } + + AZStd::optional Process::GetReturnCode() const + { + return m_returnCode; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcess.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcess.h new file mode 100644 index 0000000000..763290d787 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcess.h @@ -0,0 +1,61 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include + +namespace TestImpact +{ + //! Abstraction of platform-specific process. + class Process + { + public: + explicit Process(const ProcessInfo& processInfo); + virtual ~Process() = default; + + //! Terminates the process with the specified return code. + virtual void Terminate(ReturnCode returnCode) = 0; + + //! Block the calling thread until the process exits. + virtual void BlockUntilExit() = 0; + + //! Returns whether or not the process is still running. + virtual bool IsRunning() const = 0; + + //! Returns the process info associated with this process. + const ProcessInfo& GetProcessInfo() const; + + //! Returns the return code of the exited process. + //! Will be empty if the process is still running or was not successfully launched. + AZStd::optional GetReturnCode() const; + + //! Flushes the internal buffer and returns the process's buffered standard output. + //! Subsequent calls will keep returning data so long as the process is producing output. + //! Will return nullopt if no output routing or no output produced. + virtual AZStd::optional ConsumeStdOut() = 0; + + //! Flushes the internal buffer and returns the process's buffered standard error. + //! Subsequent calls will keep returning data so long as the process is producing errors. + //! Will return nullopt if no error routing or no errors produced. + virtual AZStd::optional ConsumeStdErr() = 0; + + protected: + //! The information used to launch the process. + ProcessInfo m_processInfo; + + //! The return code of a successfully launched process (otherwise is empty) + AZStd::optional m_returnCode; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessException.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessException.h new file mode 100644 index 0000000000..0c53800287 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessException.h @@ -0,0 +1,26 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! Exception for processes and process-related operations. + class ProcessException + : public Exception + { + public: + using Exception::Exception; + }; +} diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessInfo.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessInfo.cpp new file mode 100644 index 0000000000..8dd81e4a21 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessInfo.cpp @@ -0,0 +1,67 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +namespace TestImpact +{ + ProcessInfo::ProcessInfo(ProcessId id, const AZ::IO::Path& processPath, const AZStd::string& startupArgs) + : m_id(id) + , m_parentHasStdOutput(false) + , m_parentHasStdErr(false) + , m_processPath(processPath) + , m_startupArgs(startupArgs) + { + AZ_TestImpact_Eval(processPath.String().length() > 0, ProcessException, "Process path cannot be empty"); + } + + ProcessInfo::ProcessInfo( + ProcessId id, + StdOutputRouting stdOut, + StdErrorRouting stdErr, + const AZ::IO::Path& processPath, + const AZStd::string& startupArgs) + : m_id(id) + , m_processPath(processPath) + , m_startupArgs(startupArgs) + , m_parentHasStdOutput(stdOut == StdOutputRouting::ToParent ? true : false) + , m_parentHasStdErr(stdErr == StdErrorRouting::ToParent ? true : false) + { + AZ_TestImpact_Eval(processPath.String().length() > 0, ProcessException, "Process path cannot be empty"); + } + + ProcessId ProcessInfo::GetId() const + { + return m_id; + } + + const AZ::IO::Path& ProcessInfo::GetProcessPath() const + { + return m_processPath; + } + + const AZStd::string& ProcessInfo::GetStartupArgs() const + { + return m_startupArgs; + } + + bool ProcessInfo::ParentHasStdOutput() const + { + return m_parentHasStdOutput; + } + + bool ProcessInfo::ParentHasStdError() const + { + return m_parentHasStdErr; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessInfo.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessInfo.h new file mode 100644 index 0000000000..b73613db0d --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessInfo.h @@ -0,0 +1,93 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include + +namespace TestImpact +{ + //! Identifier to distinguish between processes. + using ProcessId = size_t; + + //! Return code of successfully launched process. + using ReturnCode = int; + + //! Error code for processes that are forcefully terminated whilst in-flight by the client. + inline constexpr const ReturnCode ProcessTerminateErrorCode = 0xF10BAD; + + //! Error code for processes that are forcefully terminated whilst in-flight by the scheduler due to timing out. + inline constexpr const ReturnCode ProcessTimeoutErrorCode = 0xBADF10; + + //! Specifier for how the process's standard out willt be routed + enum class StdOutputRouting + { + ToParent, + None + }; + + enum class StdErrorRouting + { + ToParent, + None + }; + + //! Container for process standard output and standard error. + struct StdContent + { + AZStd::optional m_out; + AZStd::optional m_err; + }; + + //! Information about a process the arguments used to launch it. + class ProcessInfo + { + public: + //! Provides the information required to launch a process. + //! @param processId Client-supplied id to diffrentiate between processes. + //! @param stdOut Routing of process standard output. + //! @param stdErr Routing of process standard error. + //! @param processPath Path to executable binary to launch. + //! @param startupArgs Arguments to launch the process with. + ProcessInfo( + ProcessId processId, + StdOutputRouting stdOut, + StdErrorRouting stdErr, + const AZ::IO::Path& processPath, + const AZStd::string& startupArgs = ""); + ProcessInfo(ProcessId processId, const AZ::IO::Path& processPath, const AZStd::string& startupArgs = ""); + + //! Returns the identifier of this process. + ProcessId GetId() const; + + //! Returns whether or not stdoutput is routed to the parent process. + bool ParentHasStdOutput() const; + + //! Returns whether or not stderror is routed to the parent process. + bool ParentHasStdError() const; + + // Returns the path to the process binary. + const AZ::IO::Path& GetProcessPath() const; + + //! Returns the command line arguments used to launch the process. + const AZStd::string& GetStartupArgs() const; + + private: + const ProcessId m_id; + const bool m_parentHasStdOutput; + const bool m_parentHasStdErr; + const AZ::IO::Path m_processPath; + const AZStd::string m_startupArgs; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessLauncher.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessLauncher.h new file mode 100644 index 0000000000..cefd16f003 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Process/TestImpactProcessLauncher.h @@ -0,0 +1,27 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include + +namespace TestImpact +{ + class Process; + class ProcessInfo; + + //! Attempts to launch a process with the provided command line arguments. + //! @param processInfo The path and command line arguments to launch the process with. + AZStd::unique_ptr LaunchProcess(const ProcessInfo& processInfo); +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactBuildTarget.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactBuildTarget.cpp new file mode 100644 index 0000000000..bce6fa25d6 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactBuildTarget.cpp @@ -0,0 +1,48 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include "TestImpactBuildTarget.h" + +namespace TestImpact +{ + BuildTarget::BuildTarget(BuildTargetDescriptor&& descriptor, TargetType type) + : m_buildMetaData(AZStd::move(descriptor.m_buildMetaData)) + , m_sources(AZStd::move(descriptor.m_sources)) + , m_type(type) + { + } + + const AZStd::string& BuildTarget::GetName() const + { + return m_buildMetaData.m_name; + } + + const AZStd::string& BuildTarget::GetOutputName() const + { + return m_buildMetaData.m_outputName; + } + + const AZ::IO::Path& BuildTarget::GetPath() const + { + return m_buildMetaData.m_path; + } + + const TargetSources& BuildTarget::GetSources() const + { + return m_sources; + } + + TargetType BuildTarget::GetType() const + { + return m_type; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactBuildTarget.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactBuildTarget.h new file mode 100644 index 0000000000..d4346c15ef --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactBuildTarget.h @@ -0,0 +1,55 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include + +namespace TestImpact +{ + //! Type id for querying specialized derived target types from base pointer/reference. + enum class TargetType : bool + { + Production, //!< Production build target. + Test //!< Test build target. + }; + + //! Representation of a generic build target in the repository. + class BuildTarget + { + public: + BuildTarget(BuildTargetDescriptor&& descriptor, TargetType type); + virtual ~BuildTarget() = default; + + //! Returns the build target name. + const AZStd::string& GetName() const; + + //! Returns the build target's compiled binary name. + const AZStd::string& GetOutputName() const; + + //! Returns the path in the source tree to the build target location. + const AZ::IO::Path& GetPath() const; + + //! Returns the build target's sources. + const TargetSources& GetSources() const; + + //! Returns the build target type. + TargetType GetType() const; + + private: + BuildMetaData m_buildMetaData; + TargetSources m_sources; + TargetType m_type; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactBuildTargetList.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactBuildTargetList.h new file mode 100644 index 0000000000..dfd102b55f --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactBuildTargetList.h @@ -0,0 +1,125 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include +#include +#include +#include + +#include + +namespace TestImpact +{ + //! Container for unique set of sorted build target types. + //! @tparam Target The specialized build target type. + template + class BuildTargetList + { + public: + using TargetType = Target; + + BuildTargetList(AZStd::vector&& descriptors); + + //! Returns the targets in the collection. + const AZStd::vector& GetTargets() const; + + //! Returns the target with the specified name. + const Target* GetTarget(const AZStd::string& name) const; + + //! Returns the target with the specified name or throws if target not found. + const Target* GetTargetOrThrow(const AZStd::string& name) const; + + // Returns the number of targets in the list. + size_t GetNumTargets() const; + + private: + AZStd::vector m_targets; + }; + + template + BuildTargetList::BuildTargetList(AZStd::vector&& descriptors) + { + AZ_TestImpact_Eval(!descriptors.empty(), TargetException, "Target list is empty"); + + AZStd::sort( + descriptors.begin(), descriptors.end(), [](const typename Target::Descriptor& lhs, const typename Target::Descriptor& rhs) + { + return lhs.m_buildMetaData.m_name < rhs.m_buildMetaData.m_name; + }); + + const auto duplicateElement = AZStd::adjacent_find( + descriptors.begin(), descriptors.end(), [](const typename Target::Descriptor& lhs, const typename Target::Descriptor& rhs) + { + return lhs.m_buildMetaData.m_name == rhs.m_buildMetaData.m_name; + }); + + AZ_TestImpact_Eval(duplicateElement == descriptors.end(), TargetException, "Target list contains duplicate targets"); + + m_targets.reserve(descriptors.size()); + for (auto&& descriptor : descriptors) + { + m_targets.emplace_back(Target(AZStd::move(descriptor))); + } + } + + template + const AZStd::vector& BuildTargetList::GetTargets() const + { + return m_targets; + } + + template + size_t BuildTargetList::GetNumTargets() const + { + return m_targets.size(); + } + + template + const Target* BuildTargetList::GetTarget(const AZStd::string& name) const + { + struct TargetComparator + { + bool operator()(const Target& target, const AZStd::string& name) const + { + return target.GetName() < name; + } + + bool operator()(const AZStd::string& name, const Target& target) const + { + return name < target.GetName(); + } + }; + + const auto targetRange = std::equal_range(m_targets.begin(), m_targets.end(), name, TargetComparator{}); + + if (targetRange.first != targetRange.second) + { + return targetRange.first; + } + else + { + return nullptr; + } + } + + template + const Target* BuildTargetList::GetTargetOrThrow(const AZStd::string& name) const + { + const Target* target = GetTarget(name); + AZ_TestImpact_Eval(target, TargetException, AZStd::string::format("Couldn't find target %s", name.c_str()).c_str()); + return target; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactProductionTarget.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactProductionTarget.cpp new file mode 100644 index 0000000000..88c3cf932a --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactProductionTarget.cpp @@ -0,0 +1,21 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include "TestImpactProductionTarget.h" + +namespace TestImpact +{ + ProductionTarget::ProductionTarget(Descriptor&& descriptor) + : BuildTarget(AZStd::move(descriptor), TargetType::Production) + { + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactProductionTarget.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactProductionTarget.h new file mode 100644 index 0000000000..d4ddb6dc50 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactProductionTarget.h @@ -0,0 +1,31 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +namespace TestImpact +{ + //! Build target specialization for production targets (build targets containing production code and no test code). + class ProductionTarget + : public BuildTarget + { + public: + using Descriptor = ProductionTargetDescriptor; + ProductionTarget(Descriptor&& descriptor); + }; + + template + inline constexpr bool IsProductionTarget = AZStd::is_same_v>>>; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactProductionTargetList.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactProductionTargetList.h new file mode 100644 index 0000000000..d2f23abe2e --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactProductionTargetList.h @@ -0,0 +1,22 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +namespace TestImpact +{ + //! Container for set of sorted production targets containing no duplicates. + using ProductionTargetList = BuildTargetList; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTargetException.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTargetException.h new file mode 100644 index 0000000000..d3f2ec25ee --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTargetException.h @@ -0,0 +1,25 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! Exception for target and target-related operations. + class TargetException : public Exception + { + public: + using Exception::Exception; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTestTarget.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTestTarget.cpp new file mode 100644 index 0000000000..0189bc9e78 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTestTarget.cpp @@ -0,0 +1,32 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include "TestImpactTestTarget.h" + +namespace TestImpact +{ + TestTarget::TestTarget(Descriptor&& descriptor) + : BuildTarget(AZStd::move(descriptor), TargetType::Test) + , m_testMetaData(AZStd::move(descriptor.m_testMetaData)) + { + } + + const AZStd::string& TestTarget::GetSuite() const + { + return m_testMetaData.m_suite; + } + + LaunchMethod TestTarget::GetLaunchMethod() const + { + return m_testMetaData.m_launchMethod; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTestTarget.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTestTarget.h new file mode 100644 index 0000000000..b6f44e7cc1 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTestTarget.h @@ -0,0 +1,41 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +namespace TestImpact +{ + //! Build target specialization for test targets (build targets containing test code and no production code). + class TestTarget + : public BuildTarget + { + public: + using Descriptor = TestTargetDescriptor; + + TestTarget(Descriptor&& descriptor); + + //! Returns the test target suite. + const AZStd::string& GetSuite() const; + + //! Returns the test target launch method. + LaunchMethod GetLaunchMethod() const; + + private: + const TestTargetMeta m_testMetaData; + }; + + template + inline constexpr bool IsTestTarget = AZStd::is_same_v>>>; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTestTargetList.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTestTargetList.h new file mode 100644 index 0000000000..f8cafcb735 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Target/TestImpactTestTargetList.h @@ -0,0 +1,22 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +namespace TestImpact +{ + //! Container for set of sorted test targets containing no duplicates. + using TestTargetList = BuildTargetList; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumeration.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumeration.h new file mode 100644 index 0000000000..c12abec006 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumeration.h @@ -0,0 +1,22 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +namespace TestImpact +{ + //! Representation of a given test target's enumerated tests. + using TestEnumeration = TestSuiteContainer; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerationException.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerationException.h new file mode 100644 index 0000000000..0c9d27df7a --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerationException.h @@ -0,0 +1,26 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! Exception for test enumerations and test enumeration related operations. + class TestEnumerationException + : public Exception + { + public: + using Exception::Exception; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerationSerializer.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerationSerializer.cpp new file mode 100644 index 0000000000..0b171ba404 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerationSerializer.cpp @@ -0,0 +1,100 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include +#include +#include + +namespace TestImpact +{ + namespace + { + // Keys for pertinent JSON node and attribute names + constexpr const char* Keys[] = + { + "suites", + "name", + "enabled", + "tests" + }; + + enum + { + SuitesKey, + NameKey, + EnabledKey, + TestsKey + }; + } // namespace + + AZStd::string SerializeTestEnumeration(const TestEnumeration& testEnum) + { + rapidjson::StringBuffer stringBuffer; + rapidjson::PrettyWriter writer(stringBuffer); + + writer.StartObject(); + writer.Key(Keys[SuitesKey]); + writer.StartArray(); + for (const auto& suite : testEnum.GetTestSuites()) + { + writer.StartObject(); + writer.Key(Keys[NameKey]); + writer.String(suite.m_name.c_str()); + writer.Key(Keys[EnabledKey]); + writer.Bool(suite.m_enabled); + writer.Key(Keys[TestsKey]); + writer.StartArray(); + for (const auto& test : suite.m_tests) + { + writer.StartObject(); + writer.Key(Keys[NameKey]); + writer.String(test.m_name.c_str()); + writer.Key(Keys[EnabledKey]); + writer.Bool(test.m_enabled); + writer.EndObject(); + } + writer.EndArray(); + writer.EndObject(); + } + writer.EndArray(); + writer.EndObject(); + + return stringBuffer.GetString(); + } + + TestEnumeration DeserializeTestEnumeration(const AZStd::string& testEnumString) + { + AZStd::vector testSuites; + rapidjson::Document doc; + + if (doc.Parse<0>(testEnumString.c_str()).HasParseError()) + { + throw TestEnumerationException("Could not parse enumeration data"); + } + + for (const auto& suite : doc[Keys[SuitesKey]].GetArray()) + { + testSuites.emplace_back(TestEnumerationSuite{suite[Keys[NameKey]].GetString(), suite[Keys[EnabledKey]].GetBool(), {}}); + for (const auto& test : suite[Keys[TestsKey]].GetArray()) + { + testSuites.back().m_tests.emplace_back( + TestEnumerationCase{test[Keys[NameKey]].GetString(), test[Keys[EnabledKey]].GetBool()}); + } + } + + return TestEnumeration(std::move(testSuites)); + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerationSerializer.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerationSerializer.h new file mode 100644 index 0000000000..422308d862 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerationSerializer.h @@ -0,0 +1,26 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include + +namespace TestImpact +{ + //! Serializes the specified test enumeration to JSON format. + AZStd::string SerializeTestEnumeration(const TestEnumeration& testEnumeration); + + //! Deserializes a test enumeration from the specified test enumeration data in JSON format. + TestEnumeration DeserializeTestEnumeration(const AZStd::string& testEnumerationString); +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerator.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerator.cpp new file mode 100644 index 0000000000..1964f8876e --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerator.cpp @@ -0,0 +1,190 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include +#include + +#include + +namespace TestImpact +{ + namespace + { + void WriteCacheFile( + const TestEnumeration& enumeration, const AZ::IO::Path& path, Bitwise::CacheExceptionPolicy cacheExceptionPolicy) + { + const AZStd::string cacheJSON = SerializeTestEnumeration(enumeration); + const AZStd::vector cacheBytes(cacheJSON.begin(), cacheJSON.end()); + AZ::IO::SystemFile cacheFile; + + if (!cacheFile.Open( + path.c_str(), + AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY)) + { + AZ_TestImpact_Eval( + !IsFlagSet(cacheExceptionPolicy, Bitwise::CacheExceptionPolicy::OnCacheWriteFailure), TestEnumerationException, + "Couldn't open cache file for writing"); + return; + } + + if (cacheFile.Write(cacheBytes.data(), cacheBytes.size()) == 0) + { + AZ_TestImpact_Eval( + !IsFlagSet(cacheExceptionPolicy, Bitwise::CacheExceptionPolicy::OnCacheWriteFailure), TestEnumerationException, + "Couldn't write cache file data"); + return; + } + } + + AZStd::optional ReadCacheFile(const AZ::IO::Path& path, Bitwise::CacheExceptionPolicy cacheExceptionPolicy) + { + AZ::IO::SystemFile cacheFile; + AZStd::string cacheJSON; + if (!cacheFile.Open(path.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY)) + { + AZ_TestImpact_Eval( + !IsFlagSet(cacheExceptionPolicy, Bitwise::CacheExceptionPolicy::OnCacheNotExist), TestEnumerationException, + "Couldn't locate cache file"); + return AZStd::nullopt; + } + + const AZ::IO::SystemFile::SizeType length = cacheFile.Length(); + if (length == 0) + { + AZ_TestImpact_Eval( + !IsFlagSet(cacheExceptionPolicy, Bitwise::CacheExceptionPolicy::OnCacheReadFailure), TestEnumerationException, + "Cache file is empty"); + return AZStd::nullopt; + } + + cacheFile.Seek(0, AZ::IO::SystemFile::SF_SEEK_BEGIN); + cacheJSON.resize(length); + if (cacheFile.Read(length, cacheJSON.data()) != length) + { + AZ_TestImpact_Eval( + !IsFlagSet(cacheExceptionPolicy, Bitwise::CacheExceptionPolicy::OnCacheReadFailure), TestEnumerationException, + "Couldn't read cache file"); + return AZStd::nullopt; + } + + return DeserializeTestEnumeration(cacheJSON); + } + } // namespace + + TestEnumeration ParseTestEnumerationFile(const AZ::IO::Path& enumerationFile) + { + return TestEnumeration(GTest::TestEnumerationSuitesFactory(ReadFileContents(enumerationFile))); + } + + TestEnumerationJobData::TestEnumerationJobData(const AZ::IO::Path& enumerationArtifact, AZStd::optional&& cache) + : m_enumerationArtifact(enumerationArtifact) + , m_cache(AZStd::move(cache)) + { + } + + const AZ::IO::Path& TestEnumerationJobData::GetEnumerationArtifactPath() const + { + return m_enumerationArtifact; + } + + const AZStd::optional& TestEnumerationJobData::GetCache() const + { + return m_cache; + } + + TestEnumerator::TestEnumerator( + AZStd::optional clientCallback, + size_t maxConcurrentEnumerations, + AZStd::optional enumerationTimeout, + AZStd::optional enumeratorTimeout) + : JobRunner(clientCallback, AZStd::nullopt, StdOutputRouting::None, StdErrorRouting::None, maxConcurrentEnumerations, enumerationTimeout, enumeratorTimeout) + { + } + + AZStd::vector TestEnumerator::Enumerate( + const AZStd::vector& jobInfos, + CacheExceptionPolicy cacheExceptionPolicy, + JobExceptionPolicy jobExceptionPolicy) + { + AZStd::vector cachedJobs; + AZStd::vector jobQueue; + + for (const auto& jobInfo : jobInfos) + { + // If this job has a cache read policy attempt to read the cache + if (jobInfo.GetCache().has_value() && jobInfo.GetCache()->m_policy == JobData::CachePolicy::Read) + { + JobMeta meta; + auto enumeration = ReadCacheFile(jobInfo.GetCache()->m_file, cacheExceptionPolicy); + + // Even though cached jobs don't get executed we still give the client the opportunity to handle the job state + // change in order to make the caching process transparent to the client + if (enumeration.has_value()) + { + if (m_clientJobCallback.has_value()) + { + (*m_clientJobCallback)(jobInfo, meta); + } + + // Cache read successfully, this job will not be placed in the job queue + cachedJobs.emplace_back(Job(jobInfo, AZStd::move(meta), AZStd::move(enumeration))); + } + else + { + // The cache read failed and exception policy for cache read failures is not to throw so instead place this job in the + // job queue + jobQueue.emplace_back(jobInfo); + } + } + else + { + // This job has no cache read policy so place in job queue + jobQueue.emplace_back(jobInfo); + } + } + + const auto payloadGenerator = [this, cacheExceptionPolicy](const JobDataMap& jobDataMap) + { + PayloadMap enumerations; + for (const auto& [jobId, jobData] : jobDataMap) + { + const auto& [meta, jobInfo] = jobData; + if (meta.m_result == JobResult::ExecutedWithSuccess) + { + const auto& enumeration = enumerations[jobId] = ParseTestEnumerationFile(jobInfo->GetEnumerationArtifactPath()); + + // Write out the enumeration to a cache file if we have a cache write policy for this job + if (jobInfo->GetCache().has_value() && jobInfo->GetCache()->m_policy == JobData::CachePolicy::Write) + { + WriteCacheFile(enumeration.value(), jobInfo->GetCache()->m_file, cacheExceptionPolicy); + } + } + } + + return enumerations; + }; + + // Generate the enumeration results for the jobs that weren't cached + auto jobs = ExecuteJobs(jobQueue, payloadGenerator, jobExceptionPolicy); + + // We need to add the cached jobs to the completed job list even though they technically weren't executed + for (auto&& job : cachedJobs) + { + jobs.emplace_back(AZStd::move(job)); + } + + return jobs; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerator.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerator.h new file mode 100644 index 0000000000..a005a32b0d --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Enumeration/TestImpactTestEnumerator.h @@ -0,0 +1,94 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include + +#include + +namespace TestImpact +{ + //! Per-job data for test enumerations. + class TestEnumerationJobData + { + public: + //! Policy for how a test enumeration will be written/read from the a previous cache instead of enumerated from the test target. + enum class CachePolicy + { + Read, //!< Do read from a cache file but do not overwrite any existing cache file. + Write //!< Do not read from a cache file but instead overwrite any existing cache file. + }; + + //! Cache configuration for a given test enumeration command. + struct Cache + { + CachePolicy m_policy; + AZ::IO::Path m_file; + }; + + TestEnumerationJobData(const AZ::IO::Path& enumerationArtifact, AZStd::optional&& cache); + + //! Returns the path to the enumeration artifact produced by the test target. + const AZ::IO::Path& GetEnumerationArtifactPath() const; + + //! Returns the cache details for this job. + const AZStd::optional& GetCache() const; + + private: + AZ::IO::Path m_enumerationArtifact; //!< Path to enumeration artifact to be processed. + AZStd::optional m_cache = AZStd::nullopt; //!< No caching takes place if cache is empty. + }; + + namespace Bitwise + { + //! Exception policy for test enumeration cache reads/writes. + enum class CacheExceptionPolicy + { + Never = 0, //! Never throw. + OnCacheNotExist = 1, //! Throw when a cache read policy was in place but a cache file for this job doesn't exist. + OnCacheReadFailure = 1 << 1, //! Throw when a cache read policy is in place but the cache file could not be read. + OnCacheWriteFailure = 1 << 2 //! Throw when a cache write policy is in place but the cache file could not be written. + }; + } // namespace Bitwise + + //! Enumerate a batch of test targets to determine the test suites and fixtures they contain, caching the results where applicable. + class TestEnumerator + : public TestJobRunner + { + using JobRunner = TestJobRunner; + + public: + using CacheExceptionPolicy = Bitwise::CacheExceptionPolicy; + + //! Constructs a test enumerator with the specified parameters common to all enumeration job runs of this enumerator. + //! @param clientCallback The optional client callback to be called whenever an enumeration job changes state. + //! @param maxConcurrentEnumerations The maximum number of enumerations to be in flight at any given time. + //! @param enumerationTimeout The maximum duration an enumeration may be in-flight for before being forcefully terminated. + //! @param enumeratorTimeout The maximum duration the enumerator may run before forcefully terminating all in-flight enumerations. + TestEnumerator( + AZStd::optional clientCallback, + size_t maxConcurrentEnumerations, + AZStd::optional enumerationTimeout, + AZStd::optional enumeratorTimeout); + + //! Executes the specified test enumeration jobs according to the specified cache and job exception policies. + //! @param jobInfos The enumeration jobs to execute. + //! @param cacheExceptionPolicy The cache exception policy to be used for this run. + //! @param jobExceptionPolicy The enumeration job exception policy to be used for this run. + //! @return the test enumeration jobs with their associated test enumeration payloads. + AZStd::vector Enumerate( + const AZStd::vector& jobInfos, CacheExceptionPolicy cacheExceptionPolicy, JobExceptionPolicy jobExceptionPolicy); + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Job/TestImpactTestJobCommon.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Job/TestImpactTestJobCommon.h new file mode 100644 index 0000000000..ee245f1ff3 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Job/TestImpactTestJobCommon.h @@ -0,0 +1,36 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include +#include +#include + +namespace TestImpact +{ + template + AZStd::string ReadFileContents(const AZ::IO::Path& file) + { + static_assert(AZStd::is_base_of::value, "Exception must be a TestImpact exception or derived type"); + + const auto fileSize = AZ::IO::SystemFile::Length(file.c_str()); + AZ_TestImpact_Eval(fileSize > 0, ExceptionType, AZStd::string::format("File %s does not exist", file.c_str())); + + AZStd::vector buffer(fileSize + 1); + buffer[fileSize] = '\0'; + AZ_TestImpact_Eval(AZ::IO::SystemFile::Read(file.c_str(), buffer.data()), ExceptionType, "Could not read file contents"); + + return AZStd::string(buffer.begin(), buffer.end()); + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Job/TestImpactTestJobException.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Job/TestImpactTestJobException.h new file mode 100644 index 0000000000..263d70322e --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Job/TestImpactTestJobException.h @@ -0,0 +1,26 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! Exception for test job related operations. + class TestJobException + : public Exception + { + public: + using Exception::Exception; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Job/TestImpactTestJobRunner.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Job/TestImpactTestJobRunner.h new file mode 100644 index 0000000000..a4a0b19845 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Job/TestImpactTestJobRunner.h @@ -0,0 +1,141 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include + +namespace TestImpact +{ + namespace Bitwise + { + //! Exception policy for test jobs (and derived jobs). + enum class TestJobExceptionPolicy + { + Never = 0, //!< Never throw. + OnFailedToExecute = 1, //!< Throw when a job fails to execute. + OnExecutedWithFailure = 1 << 1 //!< Throw when a job returns with an error code. + }; + } // namespace Bitwise + + //! Base class for test related job runners. + //! @tparam AdditionalInfo The data structure containing the information additional to the command arguments necessary to execute and + //! complete a job. + //! @tparam Payload The output produced by a job. + template + class TestJobRunner + { + public: + using JobData = AdditionalInfo; + using JobInfo = JobInfo; + using JobPayload = Payload; + using Job = Job; + using ClientJobCallback = AZStd::function; + using DerivedJobCallback = JobCallback; + using JobExceptionPolicy = Bitwise::TestJobExceptionPolicy; + using JobDataMap = JobDataMap; + + //! Constructs the job runner with the specified parameters common to all job runs of this runner. + //! @param clientCallback The optional callback function provided by the client to be called upon job state change. + //! @param clientCallback The optional callback function provided by the derived job runner to be called upon job state change. + //! @param stdOutRouting The standard output routing from the underlying job processes to the derived runner. + //! @param stdErrorRouting The standard error routing from the underlying job processes to the derived runner. + //! @param maxConcurrentJobs The maximum number of jobs to be in flight at any given time. + //! @param jobTimeout The maximum duration a job may be in-flight for before being forcefully terminated (nullopt if no timeout). + //! @param runnerTimeout The maximum duration the runner may run before forcefully terminating all in-flight jobs (nullopt if no + //! timeout). + TestJobRunner( + AZStd::optional clientCallback, + AZStd::optional derivedJobCallback, + StdOutputRouting stdOutRouting, + StdErrorRouting stdErrRouting, + size_t maxConcurrentJobs, + AZStd::optional jobTimeout, + AZStd::optional runnerTimeout); + + protected: + //! Runs the specified jobs and returns the completed payloads produced by each job. + //! @param jobInfos The batch of jobs to execute. + //! @param payloadMapProducer The client callback for producing the payload map based on the completed job data. + //! @param jobExceptionPolicy The job execution policy for this job run. + AZStd::vector ExecuteJobs( + const AZStd::vector& jobInfos, + PayloadMapProducer payloadMapProducer, + JobExceptionPolicy jobExceptionPolicy); + + const AZStd::optional m_clientJobCallback; + + private: + JobRunner m_jobRunner; + const AZStd::optional m_derivedJobCallback; + }; + + template + TestJobRunner::TestJobRunner( + AZStd::optional clientCallback, + AZStd::optional derivedJobCallback, + StdOutputRouting stdOutRouting, + StdErrorRouting stdErrRouting, + size_t maxConcurrentJobs, + AZStd::optional jobTimeout, + AZStd::optional runnerTimeout) + : m_jobRunner(stdOutRouting, stdErrRouting, maxConcurrentJobs, jobTimeout, runnerTimeout) + , m_clientJobCallback(clientCallback) + , m_derivedJobCallback(derivedJobCallback) + { + } + + template + AZStd::vector::Job> TestJobRunner::ExecuteJobs( + const AZStd::vector& jobInfos, + PayloadMapProducer payloadMapProducer, + JobExceptionPolicy jobExceptionPolicy) + { + // Callback to handle job exception policies and client/derived callbacks + const auto jobCallback = [this, &jobExceptionPolicy](const JobInfo& jobInfo, const JobMeta& meta, StdContent&& std) + { + if (meta.m_result == JobResult::FailedToExecute && IsFlagSet(jobExceptionPolicy, JobExceptionPolicy::OnFailedToExecute)) + { + throw TestJobException("Job failed to execute"); + } + else if (meta.m_result == JobResult::ExecutedWithFailure && IsFlagSet(jobExceptionPolicy, JobExceptionPolicy::OnExecutedWithFailure)) + { + throw TestJobException("Job executed with failure"); + } + + if (m_derivedJobCallback.has_value()) + { + if (const auto result = (*m_derivedJobCallback)(jobInfo, meta, AZStd::move(std)); result != CallbackResult::Continue) + { + return result; + } + } + + if (m_clientJobCallback.has_value()) + { + (*m_clientJobCallback)(jobInfo, meta); + } + + return CallbackResult::Continue; + }; + + return m_jobRunner.Execute(jobInfos, jobCallback, payloadMapProducer); + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactInstrumentedTestRunner.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactInstrumentedTestRunner.cpp new file mode 100644 index 0000000000..3b7a088fb4 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactInstrumentedTestRunner.cpp @@ -0,0 +1,89 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +namespace TestImpact +{ + InstrumentedTestRunJobData::InstrumentedTestRunJobData(const AZ::IO::Path& resultsArtifact, const AZ::IO::Path& coverageArtifact) + : TestRunJobData(resultsArtifact) + , m_coverageArtifact(coverageArtifact) + { + } + + const AZ::IO::Path& InstrumentedTestRunJobData::GetCoverageArtifactPath() const + { + return m_coverageArtifact; + } + + InstrumentedTestRunner::JobPayload ParseTestRunAndCoverageFiles( + const AZ::IO::Path& runFile, + const AZ::IO::Path& coverageFile, + AZStd::chrono::milliseconds duration, + InstrumentedTestRunner::CoverageExceptionPolicy coverageExceptionPolicy) + { + TestRun run(GTest::TestRunSuitesFactory(ReadFileContents(runFile)), duration); + AZStd::vector moduleCoverages = Cobertura::ModuleCoveragesFactory(ReadFileContents(coverageFile)); + if (moduleCoverages.empty()) + { + AZ_TestImpact_Eval( + !IsFlagSet(coverageExceptionPolicy, Bitwise::CoverageExceptionPolicy::OnEmptyCoverage), TestRunException, + "No coverage data generated"); + } + + TestCoverage coverage(AZStd::move(moduleCoverages)); + return {AZStd::move(run), AZStd::move(coverage)}; + } + + InstrumentedTestRunner::InstrumentedTestRunner( + AZStd::optional clientCallback, + size_t maxConcurrentRuns, + AZStd::optional runTimeout, + AZStd::optional runnerTimeout) + : JobRunner(clientCallback, AZStd::nullopt, StdOutputRouting::None, StdErrorRouting::None, maxConcurrentRuns, runTimeout, runnerTimeout) + { + } + + AZStd::vector InstrumentedTestRunner::RunInstrumentedTests( + const AZStd::vector& jobInfos, + CoverageExceptionPolicy coverageExceptionPolicy, + JobExceptionPolicy jobExceptionPolicy) + { + const auto payloadGenerator = [this, coverageExceptionPolicy](const JobDataMap& jobDataMap) + { + PayloadMap runs; + for (const auto& [jobId, jobData] : jobDataMap) + { + const auto& [meta, jobInfo] = jobData; + if (meta.m_result == JobResult::ExecutedWithSuccess || meta.m_result == JobResult::ExecutedWithFailure) + { + runs[jobId] = ParseTestRunAndCoverageFiles( + jobInfo->GetRunArtifactPath(), + jobInfo->GetCoverageArtifactPath(), + meta.m_duration.value(), + coverageExceptionPolicy); + } + } + + return runs; + }; + + return ExecuteJobs(jobInfos, payloadGenerator, jobExceptionPolicy); + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactInstrumentedTestRunner.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactInstrumentedTestRunner.h new file mode 100644 index 0000000000..e6f059b5d3 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactInstrumentedTestRunner.h @@ -0,0 +1,71 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace TestImpact +{ + //! Per-job data for instrumented test runs. + class InstrumentedTestRunJobData : public TestRunJobData + { + public: + InstrumentedTestRunJobData(const AZ::IO::Path& resultsArtifact, const AZ::IO::Path& coverageArtifact); + + //! Returns the path to the coverage artifact produced by the test target. + const AZ::IO::Path& GetCoverageArtifactPath() const; + + private: + AZ::IO::Path m_coverageArtifact; //!< Path to coverage data. + }; + + namespace Bitwise + { + //! Exception policy for test coverage artifacts. + enum class CoverageExceptionPolicy + { + Never = 0, //! Never throw. + OnEmptyCoverage = 1 //! Throw when no coverage data was produced. + }; + } // namespace Bitwise + + //! Runs a batch of test targets to determine the test coverage and passes/failures. + class InstrumentedTestRunner : public TestJobRunner> + { + using JobRunner = TestJobRunner>; + + public: + using CoverageExceptionPolicy = Bitwise::CoverageExceptionPolicy; + + //! Constructs an instrumented test runner with the specified parameters common to all job runs of this runner. + //! @param clientCallback The optional client callback to be called whenever a run job changes state. + //! @param maxConcurrentRuns The maximum number of runs to be in flight at any given time. + //! @param runTimeout The maximum duration a run may be in-flight for before being forcefully terminated. + //! @param runnerTimeout The maximum duration the runner may run before forcefully terminating all in-flight runs. + InstrumentedTestRunner( + AZStd::optional clientCallback, size_t maxConcurrentRuns, + AZStd::optional runTimeout, AZStd::optional runnerTimeout); + + //! Executes the specified instrumented test run jobs according to the specified job exception policies. + //! @param jobInfos The test run jobs to execute. + //! @param CoverageExceptionPolicy The coverage exception policy to be used for this run. + //! @param jobExceptionPolicy The test run job exception policy to be used for this run (use + //! TestJobExceptionPolicy::OnFailedToExecute to throw on test failures). + //! @return the instrumented test run jobs with their associated test run and test coverage payloads. + AZStd::vector RunInstrumentedTests( + const AZStd::vector& jobInfos, CoverageExceptionPolicy coverageExceptionPolicy, JobExceptionPolicy jobExceptionPolicy); + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestCoverage.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestCoverage.cpp new file mode 100644 index 0000000000..3a6f5d0b79 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestCoverage.cpp @@ -0,0 +1,68 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include + +namespace TestImpact +{ + TestCoverage::TestCoverage(AZStd::vector&& moduleCoverages) + : m_modules(AZStd::move(moduleCoverages)) + { + for (const auto& moduleCovered : m_modules) + { + for (const auto& sourceCovered : moduleCovered.m_sources) + { + m_sourcesCovered.emplace_back(sourceCovered.m_path); + if (sourceCovered.m_coverage.has_value()) + { + m_coverageLevel = CoverageLevel::Line; + } + } + } + + AZStd::sort(m_sourcesCovered.begin(), m_sourcesCovered.end()); + m_sourcesCovered.erase(AZStd::unique(m_sourcesCovered.begin(), m_sourcesCovered.end()), m_sourcesCovered.end()); + + if (!m_coverageLevel.has_value() && !m_sourcesCovered.empty()) + { + m_coverageLevel = CoverageLevel::Source; + } + } + + size_t TestCoverage::GetNumSourcesCovered() const + { + return m_sourcesCovered.size(); + } + + size_t TestCoverage::GetNumModulesCovered() const + { + return m_modules.size(); + } + + const AZStd::vector& TestCoverage::GetSourcesCovered() const + { + return m_sourcesCovered; + } + + const AZStd::vector& TestCoverage::GetModuleCoverages() const + { + return m_modules; + } + + AZStd::optional TestCoverage::GetCoverageLevel() const + { + return m_coverageLevel; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestCoverage.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestCoverage.h new file mode 100644 index 0000000000..ab92b1bd80 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestCoverage.h @@ -0,0 +1,54 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include + +namespace TestImpact +{ + //! Scope of coverage data. + enum class CoverageLevel : bool + { + Source, //!< Line-level coverage data. + Line //!< Source-level coverage data. + }; + + //! Representation of a given test target's test coverage results. + class TestCoverage + { + public: + TestCoverage(AZStd::vector&& moduleCoverages); + + //! Returns the number of unique sources covered. + size_t GetNumSourcesCovered() const; + + //! Returns the number of modules (dynamic libraries, child processes, etc.) covered. + size_t GetNumModulesCovered() const; + + //! Returns the sorted set of unique sources covered (empty if no coverage). + const AZStd::vector& GetSourcesCovered() const; + + //! Returns the modules covered (empty if no coverage). + const AZStd::vector& GetModuleCoverages() const; + + //! Returns the coverage level (empty if no coverage). + AZStd::optional GetCoverageLevel() const; + + private: + AZStd::vector m_modules; + AZStd::vector m_sourcesCovered; + AZStd::optional m_coverageLevel; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRun.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRun.cpp new file mode 100644 index 0000000000..1ba2a3e849 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRun.cpp @@ -0,0 +1,70 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include "TestImpactTestRun.h" + +namespace TestImpact +{ + TestRun::TestRun(AZStd::vector&& testSuites, AZStd::chrono::milliseconds duration) + : TestSuiteContainer(AZStd::move(testSuites)) + , m_duration(duration) + { + for (const auto& suite : m_testSuites) + { + for (const auto& test : suite.m_tests) + { + if (test.m_status == TestRunStatus::Run) + { + m_numRuns++; + + if (test.m_result.value() == TestRunResult::Passed) + { + m_numPasses++; + } + else + { + m_numFailures++; + } + } + else + { + m_numNotRuns++; + } + } + } + } + + size_t TestRun::GetNumRuns() const + { + return m_numRuns; + } + + size_t TestRun::GetNumNotRuns() const + { + return m_numNotRuns; + } + + size_t TestRun::GetNumPasses() const + { + return m_numPasses; + } + + size_t TestRun::GetNumFailures() const + { + return m_numFailures; + } + + AZStd::chrono::milliseconds TestRun::GetDuration() const + { + return m_duration; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRun.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRun.h new file mode 100644 index 0000000000..75d2140a68 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRun.h @@ -0,0 +1,51 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +namespace TestImpact +{ + //! Representation of a given test target's test run results. + class TestRun + : public TestSuiteContainer + { + using TestSuiteContainer = TestSuiteContainer; + + public: + TestRun(AZStd::vector&& testSuites, AZStd::chrono::milliseconds duration); + + //! Returns the total number of tests that were run. + size_t GetNumRuns() const; + + //! Returns the total number of tests that were not run. + size_t GetNumNotRuns() const; + + //! Returns the total number of tests that were run and passed. + size_t GetNumPasses() const; + + //! Returns the total number of tests that were run and failed. + size_t GetNumFailures() const; + + //! Returns the duration of the job that was executed to yield this run data. + AZStd::chrono::milliseconds GetDuration() const; + + private: + size_t m_numRuns = 0; + size_t m_numNotRuns = 0; + size_t m_numPasses = 0; + size_t m_numFailures = 0; + AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds{0}; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunException.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunException.h new file mode 100644 index 0000000000..ec13a17f57 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunException.h @@ -0,0 +1,25 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! Exception for test runs and test run related operations. + class TestRunException : public Exception + { + public: + using Exception::Exception; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunJobData.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunJobData.cpp new file mode 100644 index 0000000000..fa40b30263 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunJobData.cpp @@ -0,0 +1,26 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +namespace TestImpact +{ + TestRunJobData::TestRunJobData(const AZ::IO::Path& resultsArtifact) + : m_runArtifact(resultsArtifact) + { + } + + const AZ::IO::Path& TestRunJobData::GetRunArtifactPath() const + { + return m_runArtifact; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunJobData.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunJobData.h new file mode 100644 index 0000000000..802aa0d30f --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunJobData.h @@ -0,0 +1,31 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! Per-job data for test runs. + class TestRunJobData + { + public: + TestRunJobData(const AZ::IO::Path& resultsArtifact); + + //! Returns the path to the test run artifact produced by the test target. + const AZ::IO::Path& GetRunArtifactPath() const; + + private: + AZ::IO::Path m_runArtifact; //!< Path to results data. + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunSerializer.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunSerializer.cpp new file mode 100644 index 0000000000..15a135ad25 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunSerializer.cpp @@ -0,0 +1,186 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include +#include +#include + +namespace TestImpact +{ + namespace + { + // Keys for pertinent JSON node and attribute names + constexpr const char* Keys[] = + { + "suites", + "name", + "enabled", + "tests", + "duration", + "status", + "result" + }; + + enum + { + SuitesKey, + NameKey, + EnabledKey, + TestsKey, + DurationKey, + StatusKey, + ResultKey + }; + } // namespace + + AZStd::string SerializeTestRun(const TestRun& testRun) + { + rapidjson::StringBuffer stringBuffer; + rapidjson::PrettyWriter writer(stringBuffer); + + // Run + writer.StartObject(); + + // Run duration + writer.Key(Keys[DurationKey]); + writer.Uint(testRun.GetDuration().count()); + + // Suites + writer.Key(Keys[SuitesKey]); + writer.StartArray(); + + for (const auto& suite : testRun.GetTestSuites()) + { + // Suite + writer.StartObject(); + + // Suite name + writer.Key(Keys[NameKey]); + writer.String(suite.m_name.c_str()); + + // Suite duration + writer.Key(Keys[DurationKey]); + writer.Uint(suite.m_duration.count()); + + // Suite enabled + writer.Key(Keys[EnabledKey]); + writer.Bool(suite.m_enabled); + + // Suite tests + writer.Key(Keys[TestsKey]); + writer.StartArray(); + for (const auto& test : suite.m_tests) + { + // Test + writer.StartObject(); + + // Test name + writer.Key(Keys[NameKey]); + writer.String(test.m_name.c_str()); + + // Test enabled + writer.Key(Keys[EnabledKey]); + writer.Bool(test.m_enabled); + + // Test duration + writer.Key(Keys[DurationKey]); + writer.Uint(test.m_duration.count()); + + // Test status + writer.Key(Keys[StatusKey]); + writer.Bool(static_cast(test.m_status)); + + // Test result + if (test.m_status == TestRunStatus::Run) + { + writer.Key(Keys[ResultKey]); + writer.Bool(static_cast(test.m_result.value())); + } + else + { + writer.Key(Keys[ResultKey]); + writer.Null(); + } + + // End test + writer.EndObject(); + } + + // End tests + writer.EndArray(); + + // End suite + writer.EndObject(); + } + + // End suites + writer.EndArray(); + + // End run + writer.EndObject(); + + return stringBuffer.GetString(); + } + + TestRun DeserializeTestRun(const AZStd::string& testEnumString) + { + AZStd::vector testSuites; + rapidjson::Document doc; + + if (doc.Parse<0>(testEnumString.c_str()).HasParseError()) + { + throw TestRunException("Could not parse enumeration data"); + } + + // Run duration + const AZStd::chrono::milliseconds runDuration = AZStd::chrono::milliseconds{doc[Keys[DurationKey]].GetUint()}; + + // Suites + for (const auto& suite : doc[Keys[SuitesKey]].GetArray()) + { + // Suite name + const AZStd::string name = suite[Keys[NameKey]].GetString(); + + // Suite duration + const AZStd::chrono::milliseconds suiteDuration = AZStd::chrono::milliseconds{suite[Keys[DurationKey]].GetUint()}; + + // Suite enabled + const bool enabled = suite[Keys[EnabledKey]].GetBool(); + + testSuites.emplace_back(TestRunSuite{ + suite[Keys[NameKey]].GetString(), + suite[Keys[EnabledKey]].GetBool(), + {}, + AZStd::chrono::milliseconds{suite[Keys[DurationKey]].GetUint()}}); + + // Suite tests + for (const auto& test : suite[Keys[TestsKey]].GetArray()) + { + AZStd::optional result; + TestRunStatus status = static_cast(test[Keys[StatusKey]].GetBool()); + if (status == TestRunStatus::Run) + { + result = static_cast(test[Keys[ResultKey]].GetBool()); + } + const AZStd::chrono::milliseconds testDuration = AZStd::chrono::milliseconds{test[Keys[DurationKey]].GetUint()}; + testSuites.back().m_tests.emplace_back( + TestRunCase{test[Keys[NameKey]].GetString(), test[Keys[EnabledKey]].GetBool(), result, testDuration, status}); + } + } + + return TestRun(std::move(testSuites), runDuration); + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunSerializer.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunSerializer.h new file mode 100644 index 0000000000..5978b27105 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunSerializer.h @@ -0,0 +1,26 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include + +namespace TestImpact +{ + //! Serializes the specified test run to JSON format. + AZStd::string SerializeTestRun(const TestRun& testRun); + + //! Deserializes a test run from the specified test run data in JSON format. + TestRun DeserializeTestRun(const AZStd::string& testRunString); +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunner.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunner.cpp new file mode 100644 index 0000000000..dde7dd7d47 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunner.cpp @@ -0,0 +1,58 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include +#include + +#include + +namespace TestImpact +{ + TestRun ParseTestRunFile(const AZ::IO::Path& runFile, AZStd::chrono::milliseconds duration) + { + return TestRun(GTest::TestRunSuitesFactory(ReadFileContents(runFile)), duration); + } + + TestRunner::TestRunner( + AZStd::optional clientCallback, + size_t maxConcurrentRuns, + AZStd::optional runTimeout, + AZStd::optional runnerTimeout) + : JobRunner(clientCallback, AZStd::nullopt, StdOutputRouting::None, StdErrorRouting::None, maxConcurrentRuns, runTimeout, runnerTimeout) + { + } + + AZStd::vector TestRunner::RunTests( + const AZStd::vector& jobInfos, + JobExceptionPolicy jobExceptionPolicy) + { + const auto payloadGenerator = [this](const JobDataMap& jobDataMap) + { + PayloadMap runs; + for (const auto& [jobId, jobData] : jobDataMap) + { + const auto& [meta, jobInfo] = jobData; + if (meta.m_result == JobResult::ExecutedWithSuccess || meta.m_result == JobResult::ExecutedWithFailure) + { + runs[jobId] = ParseTestRunFile(jobInfo->GetRunArtifactPath(), meta.m_duration.value()); + } + } + + return runs; + }; + + return ExecuteJobs(jobInfos, payloadGenerator, jobExceptionPolicy); + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunner.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunner.h new file mode 100644 index 0000000000..0bf66601fe --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/Run/TestImpactTestRunner.h @@ -0,0 +1,46 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include + +namespace TestImpact +{ + //! Runs a batch of test targets to determine the test passes/failures. + class TestRunner + : public TestJobRunner + { + using JobRunner = TestJobRunner; + + public: + //! Constructs a test runner with the specified parameters common to all job runs of this runner. + //! @param clientCallback The optional client callback to be called whenever a run job changes state. + //! @param maxConcurrentRuns The maximum number of runs to be in flight at any given time. + //! @param runTimeout The maximum duration a run may be in-flight for before being forcefully terminated. + //! @param runnerTimeout The maximum duration the runner may run before forcefully terminating all in-flight runs. + TestRunner( + AZStd::optional clientCallback, + size_t maxConcurrentRuns, + AZStd::optional runTimeout, + AZStd::optional runnerTimeout); + + //! Executes the specified test run jobs according to the specified job exception policies. + //! @param jobInfos The test run jobs to execute. + //! @param jobExceptionPolicy The test run job exception policy to be used for this run (use + //! TestJobExceptionPolicy::OnFailedToExecute to throw on test failures). + //! @return the test run jobs with their associated test run payloads. + AZStd::vector RunTests(const AZStd::vector& jobInfos, JobExceptionPolicy jobExceptionPolicy); + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/TestImpactTestSuiteContainer.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/TestImpactTestSuiteContainer.h new file mode 100644 index 0000000000..c41d76bd04 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Test/TestImpactTestSuiteContainer.h @@ -0,0 +1,101 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! Encapsulation of test suites into a class with meta-data about each the suites. + //! @tparam TestSuite The test suite data structure to encapsulate. + template + class TestSuiteContainer + { + public: + TestSuiteContainer(AZStd::vector&& testSuites); + + //! Returns the test suites in this container. + const AZStd::vector& GetTestSuites() const; + + //! Returns the number of test suites in this container. + size_t GetNumTestSuites() const; + + //! Returns the total number of tests across all test suites. + size_t GetNumTests() const; + + //! Returns the total number of enabled tests across all test suites. + size_t GetNumEnabledTests() const; + + //! Returns the total number of disabled tests across all test suites. + size_t GetNumDisabledTests() const; + + protected: + AZStd::vector m_testSuites; + size_t m_numDisabledTests = 0; + size_t m_numEnabledTests = 0; + }; + + template + TestSuiteContainer::TestSuiteContainer(AZStd::vector&& testSuites) + : m_testSuites(std::move(testSuites)) + { + for (const auto& suite : m_testSuites) + { + if (suite.m_enabled) + { + const auto enabled = std::count_if(suite.m_tests.begin(), suite.m_tests.end(), [](const auto& test) + { + return test.m_enabled; + }); + + m_numEnabledTests += enabled; + m_numDisabledTests += suite.m_tests.size() - enabled; + } + else + { + // Disabled status of suites propagates down to all tests regardless of whether or not each individual test is disabled + m_numDisabledTests += suite.m_tests.size(); + } + } + } + + template + const AZStd::vector& TestSuiteContainer::GetTestSuites() const + { + return m_testSuites; + } + + template + size_t TestSuiteContainer::GetNumTests() const + { + return m_numEnabledTests + m_numDisabledTests; + } + + template + size_t TestSuiteContainer::GetNumEnabledTests() const + { + return m_numEnabledTests; + } + + template + size_t TestSuiteContainer::GetNumDisabledTests() const + { + return m_numDisabledTests; + } + + template + size_t TestSuiteContainer::GetNumTestSuites() const + { + return m_testSuites.size(); + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactException.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactException.cpp new file mode 100644 index 0000000000..804ab26e4d --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactException.cpp @@ -0,0 +1,31 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +namespace TestImpact +{ + Exception::Exception(const AZStd::string& msg) + : m_msg(msg) + { + } + + Exception::Exception(const char* msg) + : m_msg(msg) + { + } + + const char* Exception::what() const noexcept + { + return m_msg.c_str(); + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactFrameworkPath.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactFrameworkPath.cpp new file mode 100644 index 0000000000..6b5d671776 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactFrameworkPath.cpp @@ -0,0 +1,38 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +namespace TestImpact +{ + FrameworkPath::FrameworkPath(const AZ::IO::Path& absolutePath) + { + m_absolutePath = AZ::IO::Path(absolutePath).MakePreferred(); + m_relativePath = m_absolutePath.LexicallyRelative(m_absolutePath); + } + + FrameworkPath::FrameworkPath(const AZ::IO::Path& absolutePath, const FrameworkPath& relativeTo) + { + m_absolutePath = AZ::IO::Path(absolutePath).MakePreferred(); + m_relativePath = m_absolutePath.LexicallyRelative(relativeTo.Absolute()); + } + + const AZ::IO::Path& FrameworkPath::Absolute() const + { + return m_absolutePath; + } + + const AZ::IO::Path& FrameworkPath::Relative() const + { + return m_relativePath; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactBuildTargetDescriptorFactoryTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactBuildTargetDescriptorFactoryTest.cpp new file mode 100644 index 0000000000..a0a4a7309e --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactBuildTargetDescriptorFactoryTest.cpp @@ -0,0 +1,370 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include + +#include +#include + +namespace UnitTest +{ + class BuildTargetDescriptorFactoryTestFixture + : public AllocatorsTestFixture + { + protected: + const AZStd::vector m_staticExclude = {".cmake"}; + const AZStd::vector m_inputExclude = {".jinja"}; + const AZStd::string m_autogenMatcher = {"(.*)\\..*"}; + + const AZStd::vector m_autogenInputs = + { + "Gems/ScriptCanvasDiagnosticLibrary/Code/Source/Log.ScriptCanvasNode.xml", + "Gems/ScriptCanvasDiagnosticLibrary/Code/Source/DrawText.ScriptCanvasNode.xml", + "Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNode_Header.jinja", + "Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNode_Source.jinja" + }; + + const AZStd::vector m_autogenOutputs = + { + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/Log.generated.h", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/DrawText.generated.h", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/Log.generated.cpp", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/DrawText.generated.cpp" + }; + + const AZStd::vector m_staticSources = + { + "Gems/ScriptCanvasDiagnosticLibrary/Code/Source/precompiled.cpp", + "Gems/ScriptCanvasDiagnosticLibrary/Code/Source/precompiled.h", + "Gems/ScriptCanvasDiagnosticLibrary/Code/scriptcanvasdiagnosticlibrary_autogen_files.cmake", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/Log.generated.h", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/DrawText.generated.h", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/Log.generated.cpp", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/DrawText.generated.cpp" + }; + + const AZStd::vector m_expectedStaticSources = + { + "Gems/ScriptCanvasDiagnosticLibrary/Code/Source/precompiled.cpp", + "Gems/ScriptCanvasDiagnosticLibrary/Code/Source/precompiled.h", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/Log.generated.h", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/DrawText.generated.h", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/Log.generated.cpp", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/DrawText.generated.cpp" + }; + + const AZStd::string m_name = "ScriptCanvasDiagnosticLibrary.Static"; + const AZStd::string m_outputName = "ScriptCanvasDiagnosticLibrary"; + const AZStd::string m_path = "Gems/ScriptCanvasDiagnosticLibrary/Code"; + + const TestImpact::AutogenSources m_expectedAutogenSources = + { + { + "Gems/ScriptCanvasDiagnosticLibrary/Code/Source/Log.ScriptCanvasNode.xml", + { + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/Log.generated.h", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/Log.generated.cpp" + } + }, + { + "Gems/ScriptCanvasDiagnosticLibrary/Code/Source/DrawText.ScriptCanvasNode.xml", + { + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/DrawText.generated.h", + "windows_vs2019/Gems/ScriptCanvasDiagnosticLibrary/Code/Azcg/Generated/Source/DrawText.generated.cpp" + } + } + }; + }; + + TEST_F(BuildTargetDescriptorFactoryTestFixture, NoRawData_ExpectArtifactException) + { + // Given an empty raw descriptor string + const AZStd::string rawTargetDescriptor; + + try + { + // When attempting to construct the build target descriptor + const TestImpact::BuildTargetDescriptor buildTarget = + TestImpact::BuildTargetDescriptorFactory(rawTargetDescriptor, m_staticExclude, m_inputExclude, m_autogenMatcher); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(BuildTargetDescriptorFactoryTestFixture, InvalidRawData_ExpectArtifactException) + { + // Given a raw descriptor string of invalid data + const AZStd::string rawTargetDescriptor = "abcde"; + + try + { + // When attempting to construct the build target descriptor + const TestImpact::BuildTargetDescriptor buildTarget = + TestImpact::BuildTargetDescriptorFactory(rawTargetDescriptor, m_staticExclude, m_inputExclude, m_autogenMatcher); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(BuildTargetDescriptorFactoryTestFixture, NoAutogenMatcher_ExpectArtifactException) + { + // Given a valid raw descriptor string + const AZStd::string rawTargetDescriptor = + GenerateBuildTargetDescriptorString(m_name, m_outputName, m_path, m_staticSources, m_autogenInputs, m_autogenOutputs); + + try + { + // When attempting to construct the build target descriptor with an empty autogen matcher + const TestImpact::BuildTargetDescriptor buildTarget = + TestImpact::BuildTargetDescriptorFactory(rawTargetDescriptor, m_staticExclude, m_inputExclude, ""); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(BuildTargetDescriptorFactoryTestFixture, EmptyName_ExpectArtifactException) + { + // Given a invalid raw descriptor string lacking build meta-data name + const AZStd::string rawTargetDescriptor = + GenerateBuildTargetDescriptorString("", m_outputName, m_path, m_staticSources, m_autogenInputs, m_autogenOutputs); + + try + { + // When attempting to construct the build target descriptor + const TestImpact::BuildTargetDescriptor buildTarget = + TestImpact::BuildTargetDescriptorFactory(rawTargetDescriptor, m_staticExclude, m_inputExclude, m_autogenMatcher); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(BuildTargetDescriptorFactoryTestFixture, EmptyOutputName_ExpectArtifactException) + { + // Given a invalid raw descriptor string lacking build meta-data output name + const AZStd::string rawTargetDescriptor = + GenerateBuildTargetDescriptorString(m_name, "", m_path, m_staticSources, m_autogenInputs, m_autogenOutputs); + + try + { + // When attempting to construct the build target descriptor + const TestImpact::BuildTargetDescriptor buildTarget = + TestImpact::BuildTargetDescriptorFactory(rawTargetDescriptor, m_staticExclude, m_inputExclude, m_autogenMatcher); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(BuildTargetDescriptorFactoryTestFixture, EmptyPath_ExpectArtifactException) + { + // Given a invalid raw descriptor string lacking build meta-data path + const AZStd::string rawTargetDescriptor = + GenerateBuildTargetDescriptorString(m_name, m_outputName, "", m_staticSources, m_autogenInputs, m_autogenOutputs); + + try + { + // When attempting to construct the build target descriptor + const TestImpact::BuildTargetDescriptor buildTarget = + TestImpact::BuildTargetDescriptorFactory(rawTargetDescriptor, m_staticExclude, m_inputExclude, m_autogenMatcher); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(BuildTargetDescriptorFactoryTestFixture, NoStaticSources_ExpectValidDescriptor) + { + const TestImpact::BuildTargetDescriptor expectedBuiltTarget = + GenerateBuildTargetDescriptor(m_name, m_outputName, m_path, {}, m_expectedAutogenSources); + + // Given a valid raw descriptor string with no static sources + const AZStd::string rawTargetDescriptor = + GenerateBuildTargetDescriptorString(m_name, m_outputName, m_path, {}, m_autogenInputs, m_autogenOutputs); + + // When attempting to construct the build target descriptor + const TestImpact::BuildTargetDescriptor buildTarget = + TestImpact::BuildTargetDescriptorFactory(rawTargetDescriptor, {}, m_inputExclude, m_autogenMatcher); + + // Expect the constructed build target descriptor to match the specified descriptor + EXPECT_TRUE(buildTarget == expectedBuiltTarget); + } + + TEST_F(BuildTargetDescriptorFactoryTestFixture, NoAutogenSources_ExpectValidDescriptor) + { + const TestImpact::BuildTargetDescriptor expectedBuiltTarget = + GenerateBuildTargetDescriptor(m_name, m_outputName, m_path, m_expectedStaticSources, {}); + + // Given a valid raw descriptor string with no autogen sources + const AZStd::string rawTargetDescriptor = + GenerateBuildTargetDescriptorString(m_name, m_outputName, m_path, m_staticSources, {}, {}); + + // When attempting to construct the build target descriptor + const TestImpact::BuildTargetDescriptor buildTarget = + TestImpact::BuildTargetDescriptorFactory(rawTargetDescriptor, m_staticExclude, m_inputExclude, m_autogenMatcher); + + // Expect the constructed build target descriptor to match the specified descriptor + EXPECT_TRUE(buildTarget == expectedBuiltTarget); + } + + TEST_F(BuildTargetDescriptorFactoryTestFixture, NoStaticOrAutogenSources_ExpectValidDescriptor) + { + const TestImpact::BuildTargetDescriptor expectedBuiltTarget = GenerateBuildTargetDescriptor(m_name, m_outputName, m_path, {}, {}); + + // Given a valid raw descriptor string with no static or autogen sources + const AZStd::string rawTargetDescriptor = GenerateBuildTargetDescriptorString(m_name, m_outputName, m_path, {}, {}, {}); + + // When attempting to construct the build target descriptor + const TestImpact::BuildTargetDescriptor buildTarget = + TestImpact::BuildTargetDescriptorFactory(rawTargetDescriptor, m_staticExclude, m_inputExclude, m_autogenMatcher); + + // Expect the constructed build target descriptor to match the specified descriptor + EXPECT_TRUE(buildTarget == expectedBuiltTarget); + } + + TEST_F(BuildTargetDescriptorFactoryTestFixture, AutogenOutputSourcesButNoAutogenInputSources_ExpectArtifactException) + { + // Given a valid raw descriptor string with autogen output sources but no autogen input sources + const AZStd::string rawTargetDescriptor = + GenerateBuildTargetDescriptorString(m_name, m_outputName, m_path, m_staticSources, {}, m_autogenOutputs); + + try + { + // When attempting to construct the build target descriptor + const TestImpact::BuildTargetDescriptor buildTarget = + TestImpact::BuildTargetDescriptorFactory(rawTargetDescriptor, m_staticExclude, m_inputExclude, m_autogenMatcher); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(BuildTargetDescriptorFactoryTestFixture, AutogenInputSourcesButNoAutogenOutputSources_ExpectArtifactException) + { + // Given a valid raw descriptor string with autogen inout sources but no autogen output sources + const AZStd::string rawTargetDescriptor = + GenerateBuildTargetDescriptorString(m_name, m_outputName, m_path, m_staticSources, m_autogenInputs, {}); + + try + { + // When attempting to construct the build target descriptor + const TestImpact::BuildTargetDescriptor buildTarget = + TestImpact::BuildTargetDescriptorFactory(rawTargetDescriptor, m_staticExclude, m_inputExclude, m_autogenMatcher); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(BuildTargetDescriptorFactoryTestFixture, StaticAndAutogenSources_ExpectValidDescriptor) + { + const TestImpact::BuildTargetDescriptor expectedBuiltTarget = + GenerateBuildTargetDescriptor(m_name, m_outputName, m_path, m_expectedStaticSources, m_expectedAutogenSources); + + // Given a valid raw descriptor string with static and autogen sources + const AZStd::string rawTargetDescriptor = + GenerateBuildTargetDescriptorString(m_name, m_outputName, m_path, m_staticSources, m_autogenInputs, m_autogenOutputs); + + // When attempting to construct the build target descriptor + const TestImpact::BuildTargetDescriptor buildTarget = + TestImpact::BuildTargetDescriptorFactory(rawTargetDescriptor, m_staticExclude, m_inputExclude, m_autogenMatcher); + + // Expect the constructed build target descriptor to match the specified descriptor + EXPECT_TRUE(buildTarget == expectedBuiltTarget); + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactChangeListFactoryTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactChangeListFactoryTest.cpp new file mode 100644 index 0000000000..aeb8f118a4 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactChangeListFactoryTest.cpp @@ -0,0 +1,288 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include + +namespace UnitTest +{ + TEST(ChangeListFactoryTest, NoRawData_ExpectArtifactException) + { + // Given an empty unified diff string + const AZStd::string unifiedDiff; + + try + { + // When attempting to construct the change list + const TestImpact::ChangeList changeList = TestImpact::UnifiedDiff::ChangeListFactory(unifiedDiff); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(ChangeListFactoryTest, NoChanges_ExpectArtifactException) + { + // Given a unified diff string with no changes + const AZStd::string unifiedDiff = "On this day in 1738 absolutely nothing happened"; + + try + { + // When attempting to construct the change list + const TestImpact::ChangeList changeList = TestImpact::UnifiedDiff::ChangeListFactory(unifiedDiff); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(ChangeListFactoryTest, CreateOnly_ExpectValidChangeListWithFileCreateOperations) + { + // Given a unified diff with only one file creation and no file updates or deletions + const AZStd::string unifiedDiff = + "From f642a2f698452fc18484758b0046132415f09467 Mon Sep 17 00:00:00 2001\n" + "From: user \n" + "Date: Sat, 13 Mar 2021 22:58:07 +0000\n" + "Subject: Test\n" + "\n" + "---\n" + " New.txt | 1 +\n" + " create mode 100644 New.txt\n" + "diff --git a/New.txt b/New.txt\n" + "new file mode 100644\n" + "index 0000000..30d74d2\n" + "--- /dev/null\n" + "+++ b/New.txt\n" + "@@ -0,0 +1 @@\n" + "+test\n" + "\\ No newline at end of file\n" + "-- \n" + "2.30.0.windows.2\n" + "\n" + "\n"; + + // When attempting to construct the change list + const TestImpact::ChangeList changeList = TestImpact::UnifiedDiff::ChangeListFactory(unifiedDiff); + + // Expect the change list to contain the 1 created file + EXPECT_EQ(changeList.m_createdFiles.size(), 1); + EXPECT_TRUE( + AZStd::find(changeList.m_createdFiles.begin(), changeList.m_createdFiles.end(), "New.txt") != changeList.m_createdFiles.end()); + + // Expect the change list to contain no updated files + EXPECT_TRUE(changeList.m_updatedFiles.empty()); + + // Expect the change list to contain no deleted files + EXPECT_TRUE(changeList.m_deletedFiles.empty()); + } + + TEST(ChangeListFactoryTest, UpdateOnly_ExpectValidChangeListWithFileUpdateOperations) + { + // Given a unified diff with only one file update and no file creations or deletions + const AZStd::string unifiedDiff = + "From f642a2f698452fc18484758b0046132415f09467 Mon Sep 17 00:00:00 2001\n" + "From: user \n" + "Date: Sat, 13 Mar 2021 22:58:07 +0000\n" + "Subject: Test\n" + "\n" + "---\n" + " A.txt | 2 +-\n" + "diff --git a/A.txt b/A.txt\n" + "index 7c4a013..e132db2 100644\n" + "--- a/A.txt\n" + "+++ b/A.txt\n" + "@@ -1 +1 @@\n" + "-aaa\n" + "\\ No newline at end of file\n" + "+zzz\n" + "\\ No newline at end of file\n" + "-- \n" + "2.30.0.windows.2\n" + "\n" + "\n"; + + // When attempting to construct the change list + const TestImpact::ChangeList changeList = TestImpact::UnifiedDiff::ChangeListFactory(unifiedDiff); + + // Expect the change list to contain no created files + EXPECT_TRUE(changeList.m_createdFiles.empty()); + + // Expect the change list to contain one updated file + EXPECT_EQ(changeList.m_updatedFiles.size(), 1); + EXPECT_TRUE( + AZStd::find(changeList.m_updatedFiles.begin(), changeList.m_updatedFiles.end(), "A.txt") != changeList.m_updatedFiles.end()); + + // Expect the change list to contain no deleted files + EXPECT_TRUE(changeList.m_deletedFiles.empty()); + } + + TEST(ChangeListFactoryTest, DeleteOnly_ExpectValidChangeListWithFileDeleteOperations) + { + // Given a unified diff with only one file deletion and no file creations or updates + const AZStd::string unifiedDiff = + "From f642a2f698452fc18484758b0046132415f09467 Mon Sep 17 00:00:00 2001\n" + "From: user \n" + "Date: Sat, 13 Mar 2021 22:58:07 +0000\n" + "Subject: Test\n" + "\n" + "---\n" + " B.txt | 1 -\n" + " delete mode 100644 B.txt\n" + "diff --git a/B.txt b/B.txt\n" + "deleted file mode 100644\n" + "index 01f02e3..0000000\n" + "--- a/B.txt\n" + "+++ /dev/null\n" + "@@ -1 +0,0 @@\n" + "-bbb\n" + "\\ No newline at end of file\n" + "-- \n" + "2.30.0.windows.2\n" + "\n" + "\n"; + + // When attempting to construct the change list + const TestImpact::ChangeList changeList = TestImpact::UnifiedDiff::ChangeListFactory(unifiedDiff); + + // Expect the change list to contain no created files + EXPECT_TRUE(changeList.m_createdFiles.empty()); + + // Expect the change list to contain no updated files + EXPECT_TRUE(changeList.m_updatedFiles.empty()); + + // Expect the change list to contain one deleted file + EXPECT_EQ(changeList.m_deletedFiles.size(), 1); + EXPECT_TRUE( + AZStd::find(changeList.m_deletedFiles.begin(), changeList.m_deletedFiles.end(), "B.txt") != changeList.m_deletedFiles.end()); + } + + TEST(ChangeListFactoryTest, ParseUnifiedDiffWithAllPossibleOperations_ExpectChangeListMatchingOperations) + { + // Given a unified diff with created files, updated files, deleted files, renamed files and moved files + const AZStd::string unifiedDiff = + "From f642a2f698452fc18484758b0046132415f09467 Mon Sep 17 00:00:00 2001\n" + "From: user \n" + "Date: Sat, 13 Mar 2021 22:58:07 +0000\n" + "Subject: Test\n" + "\n" + "---\n" + " A.txt | 2 +-\n" + " B.txt | 1 -\n" + " D.txt => Foo/D.txt | 0\n" + " E.txt => Foo/Y.txt | 0\n" + " New.txt | 1 +\n" + " C.txt => X.txt | 0\n" + " 6 files changed, 2 insertions(+), 2 deletions(-)\n" + " delete mode 100644 B.txt\n" + " rename D.txt => Foo/D.txt (100%)\n" + " rename E.txt => Foo/Y.txt (100%)\n" + " create mode 100644 New.txt\n" + " rename C.txt => X.txt (100%)\n" + "\n" + "diff --git a/A.txt b/A.txt\n" + "index 7c4a013..e132db2 100644\n" + "--- a/A.txt\n" + "+++ b/A.txt\n" + "@@ -1 +1 @@\n" + "-aaa\n" + "\\ No newline at end of file\n" + "+zzz\n" + "\\ No newline at end of file\n" + "diff --git a/B.txt b/B.txt\n" + "deleted file mode 100644\n" + "index 01f02e3..0000000\n" + "--- a/B.txt\n" + "+++ /dev/null\n" + "@@ -1 +0,0 @@\n" + "-bbb\n" + "\\ No newline at end of file\n" + "diff --git a/D.txt b/Foo/D.txt\n" + "similarity index 100%\n" + "rename from D.txt\n" + "rename to Foo/D.txt\n" + "diff --git a/E.txt b/Foo/Y.txt\n" + "similarity index 100%\n" + "rename from E.txt\n" + "rename to Foo/Y.txt\n" + "diff --git a/New.txt b/New.txt\n" + "new file mode 100644\n" + "index 0000000..30d74d2\n" + "--- /dev/null\n" + "+++ b/New.txt\n" + "@@ -0,0 +1 @@\n" + "+test\n" + "\\ No newline at end of file\n" + "diff --git a/C.txt b/X.txt\n" + "similarity index 100%\n" + "rename from C.txt\n" + "rename to X.txt\n" + "-- \n" + "2.30.0.windows.2\n" + "\n" + "\n"; + + // When attempting to construct the change list + const TestImpact::ChangeList changeList = TestImpact::UnifiedDiff::ChangeListFactory(unifiedDiff); + + // Expect the change list to contain the 4 created files + EXPECT_EQ(changeList.m_createdFiles.size(), 4); + EXPECT_TRUE( + AZStd::find(changeList.m_createdFiles.begin(), changeList.m_createdFiles.end(), "Foo/D.txt") != + changeList.m_createdFiles.end()); + EXPECT_TRUE( + AZStd::find(changeList.m_createdFiles.begin(), changeList.m_createdFiles.end(), "Foo/Y.txt") != + changeList.m_createdFiles.end()); + EXPECT_TRUE( + AZStd::find(changeList.m_createdFiles.begin(), changeList.m_createdFiles.end(), "X.txt") != changeList.m_createdFiles.end()); + EXPECT_TRUE( + AZStd::find(changeList.m_createdFiles.begin(), changeList.m_createdFiles.end(), "New.txt") != changeList.m_createdFiles.end()); + + // Expect the change list to contain the 1 updated file + EXPECT_EQ(changeList.m_updatedFiles.size(), 1); + EXPECT_TRUE( + AZStd::find(changeList.m_updatedFiles.begin(), changeList.m_updatedFiles.end(), "A.txt") != changeList.m_updatedFiles.end()); + + // Expect the change list to contain the 4 deleted files + EXPECT_EQ(changeList.m_deletedFiles.size(), 4); + EXPECT_TRUE( + AZStd::find(changeList.m_deletedFiles.begin(), changeList.m_deletedFiles.end(), "B.txt") != changeList.m_deletedFiles.end()); + EXPECT_TRUE( + AZStd::find(changeList.m_deletedFiles.begin(), changeList.m_deletedFiles.end(), "D.txt") != changeList.m_deletedFiles.end()); + EXPECT_TRUE( + AZStd::find(changeList.m_deletedFiles.begin(), changeList.m_deletedFiles.end(), "E.txt") != changeList.m_deletedFiles.end()); + EXPECT_TRUE( + AZStd::find(changeList.m_deletedFiles.begin(), changeList.m_deletedFiles.end(), "C.txt") != changeList.m_deletedFiles.end()); + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactModuleCoverageFactoryTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactModuleCoverageFactoryTest.cpp new file mode 100644 index 0000000000..137addc360 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactModuleCoverageFactoryTest.cpp @@ -0,0 +1,522 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include + +#include +#include + +namespace UnitTest +{ + TEST(CoberturaModuleCoveragesFactory, ParseEmptyString_ThrowsArtifactException) + { + // Given an empty string + const AZStd::string rawCoverage; + + try + { + // When attempting to parse the empty coverage + const AZStd::vector coverage = TestImpact::Cobertura::ModuleCoveragesFactory(rawCoverage); + + // Do not expect the parsing to succeed + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(CoberturaModuleCoveragesFactory, ParseInvalidString_ThrowsArtifactException) + { + // Given an invalid string + const AZStd::string rawCoverage = "!@?"; + + try + { + // When attempting to parse the invalid coverage + const AZStd::vector coverage = TestImpact::Cobertura::ModuleCoveragesFactory(rawCoverage); + + // Do not expect the parsing to succeed + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(CoberturaModuleCoveragesFactory, ParseEmptyCoverage_ExpectEmptyModuleCoverages) + { + // Given an empty coverage string + const AZStd::string rawCoverage = + "" + "" + " " + " " + "" + ""; + + // When attempting to parse the empty coverage + const AZStd::vector coverage = TestImpact::Cobertura::ModuleCoveragesFactory(rawCoverage); + + // Expect an empty module coverages + EXPECT_TRUE(coverage.empty()); + } + + TEST(CoberturaModuleCoveragesFactory, ParseTestTargetLineCoverageA_ReturnsValidLineCoverage) + { + const AZStd::vector expectedCoverage = GetTestTargetALineModuleCoverages(); + + // Given the raw line coverage output of TestTargetA + const AZStd::string rawCoverage = + "" + "" + " " + " C:" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + + // When the raw line coverage text is parsed + const AZStd::vector coverage = TestImpact::Cobertura::ModuleCoveragesFactory(rawCoverage); + + // Expect the generated suite data to match that of the raw coverage text + EXPECT_TRUE(coverage == expectedCoverage); + } + + TEST(CoberturaModuleCoveragesFactory, ParseTestTargetSourceCoverageA_ReturnsValidSourceCoverage) + { + const AZStd::vector expectedCoverage = GetTestTargetASourceModuleCoverages(); + + // Given the raw source coverage output of TestTargetA + const AZStd::string rawCoverage = + "" + "" + " " + " C:" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + + // When the raw source coverage text is parsed + const AZStd::vector coverage = TestImpact::Cobertura::ModuleCoveragesFactory(rawCoverage); + + // Expect the generated suite data to match that of the raw coverage text + EXPECT_TRUE(coverage == expectedCoverage); + } + + TEST(CoberturaModuleCoveragesFactory, ParseTestTargetLineCoverageB_ReturnsValidLineCoverage) + { + const AZStd::vector expectedCoverage = GetTestTargetBLineModuleCoverages(); + + // Given the raw line coverage output of TestTargetB + const AZStd::string rawCoverage = + "" + "" + " " + " C:" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + + // When the raw line coverage text is parsed + const AZStd::vector coverage = TestImpact::Cobertura::ModuleCoveragesFactory(rawCoverage); + + // Expect the generated suite data to match that of the raw coverage text + EXPECT_TRUE(coverage == expectedCoverage); + } + + TEST(CoberturaModuleCoveragesFactory, ParseTestTargetSourceCoverageB_ReturnsValidSourceCoverage) + { + const AZStd::vector expectedCoverage = GetTestTargetBSourceModuleCoverages(); + + // Given the raw source coverage output of TestTargetB + const AZStd::string rawCoverage = + "" + "" + " " + " C:" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + + // When the raw source coverage text is parsed + const AZStd::vector coverage = TestImpact::Cobertura::ModuleCoveragesFactory(rawCoverage); + + // Expect the generated suite data to match that of the raw coverage text + EXPECT_TRUE(coverage == expectedCoverage); + } + + TEST(CoberturaModuleCoveragesFactory, ParseTestTargetLineCoverageC_ReturnsValidLineCoverage) + { + const AZStd::vector expectedCoverage = GetTestTargetCLineModuleCoverages(); + + // Given the raw line coverage output of TestTargetC + const AZStd::string rawCoverage = + "" + "" + " " + " C:" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + + // When the raw line coverage text is parsed + const AZStd::vector coverage = TestImpact::Cobertura::ModuleCoveragesFactory(rawCoverage); + + // Expect the generated suite data to match that of the raw coverage text + EXPECT_TRUE(coverage == expectedCoverage); + } + + TEST(CoberturaModuleCoveragesFactory, ParseTestTargetSourceCoverageC_ReturnsValidSourceCoverage) + { + const AZStd::vector expectedCoverage = GetTestTargetCSourceModuleCoverages(); + + // Given the raw source coverage output of TestTargetC + const AZStd::string rawCoverage = + "" + "" + " " + " C:" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + + // When the raw source coverage text is parsed + const AZStd::vector coverage = TestImpact::Cobertura::ModuleCoveragesFactory(rawCoverage); + + // Expect the generated suite data to match that of the raw coverage text + EXPECT_TRUE(coverage == expectedCoverage); + } + + TEST(CoberturaModuleCoveragesFactory, ParseTestTargetLineCoverageD_ReturnsValidLineCoverage) + { + const AZStd::vector expectedCoverage = GetTestTargetDLineModuleCoverages(); + + // Given the raw line coverage output of TestTargetD + const AZStd::string rawCoverage = + "" + "" + " " + " C:" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + + // When the raw line coverage text is parsed + const AZStd::vector coverage = TestImpact::Cobertura::ModuleCoveragesFactory(rawCoverage); + + // Expect the generated suite data to match that of the raw coverage text + EXPECT_TRUE(coverage == expectedCoverage); + } + + TEST(CoberturaModuleCoveragesFactory, ParseTestTargetSourceCoverageD_ReturnsValidSourceCoverage) + { + const AZStd::vector expectedCoverage = GetTestTargetDSourceModuleCoverages(); + + // Given the raw source coverage output of TestTargetD + const AZStd::string rawCoverage = + "" + "" + " " + " C:" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + + // When the raw source coverage text is parsed + const AZStd::vector coverage = TestImpact::Cobertura::ModuleCoveragesFactory(rawCoverage); + + // Expect the generated suite data to match that of the raw coverage text + EXPECT_TRUE(coverage == expectedCoverage); + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTargetDescriptorCompilerTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTargetDescriptorCompilerTest.cpp new file mode 100644 index 0000000000..1e72902b4e --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTargetDescriptorCompilerTest.cpp @@ -0,0 +1,150 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include + +#include +#include + +namespace UnitTest +{ + class TargetDescriptorCompilerTestFixture + : public AllocatorsTestFixture + { + public: + void SetUp() override + { + AllocatorsTestFixture::SetUp(); + + m_buildTargetDescriptors.emplace_back(TestImpact::BuildTargetDescriptor{{"TestTargetA", "", ""}, {}}); + m_buildTargetDescriptors.emplace_back(TestImpact::BuildTargetDescriptor{{"TestTargetB", "", ""}, {}}); + m_buildTargetDescriptors.emplace_back(TestImpact::BuildTargetDescriptor{{"ProductionTargetA", "", ""}, {}}); + m_buildTargetDescriptors.emplace_back(TestImpact::BuildTargetDescriptor{{"ProductionTargetB", "", ""}, {}}); + m_buildTargetDescriptors.emplace_back(TestImpact::BuildTargetDescriptor{{"ProductionTargetC", "", ""}, {}}); + + m_testTargetMetaMap.emplace("TestTargetA", TestImpact::TestTargetMeta{"", TestImpact::LaunchMethod::TestRunner}); + m_testTargetMetaMap.emplace("TestTargetB", TestImpact::TestTargetMeta{"", TestImpact::LaunchMethod::StandAlone}); + } + + protected: + AZStd::vector m_buildTargetDescriptors; + TestImpact::TestTargetMetaMap m_testTargetMetaMap; + }; + + TestImpact::ProductionTargetDescriptor ConstructProductionTargetDescriptor(const AZStd::string& name) + { + return TestImpact::ProductionTargetDescriptor{TestImpact::BuildTargetDescriptor{{name, "", ""}, {{}, {}}}}; + } + + TestImpact::TestTargetDescriptor ConstructTestTargetDescriptor(const AZStd::string& name, TestImpact::LaunchMethod launchMethod) + { + return TestImpact::TestTargetDescriptor{ + TestImpact::BuildTargetDescriptor{{name, "", ""}, {{}, {}}}, TestImpact::TestTargetMeta{"", launchMethod}}; + } + + TEST_F(TargetDescriptorCompilerTestFixture, EmptyBuildTargetDescriptorList_ExpectArtifactException) + { + try + { + // Given an empty build target descriptor list but valid test target meta map + // When attempting to construct the test target + const auto& [productionTargetDescriptors, testTargetDescriptors] = + TestImpact::CompileTargetDescriptors({}, AZStd::move(m_testTargetMetaMap)); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(TargetDescriptorCompilerTestFixture, EmptyTestTargetMetaMap_ExpectArtifactException) + { + try + { + // Given a valid build target descriptor list but empty test target meta map + // When attempting to construct the test target + const auto& [productionTargetDescriptors, testTargetDescriptors] = + TestImpact::CompileTargetDescriptors(AZStd::move(m_buildTargetDescriptors), {}); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(TargetDescriptorCompilerTestFixture, TestTargetWithNoMatchingMeta_ExpectArtifactException) + { + try + { + // Given a valid build target descriptor list but a test target meta map with an orphan entry + m_testTargetMetaMap.emplace("Orphan", TestImpact::TestTargetMeta{"", TestImpact::LaunchMethod::TestRunner}); + + // When attempting to construct the test target + const auto& [productionTargetDescriptors, testTargetDescriptors] = + TestImpact::CompileTargetDescriptors(AZStd::move(m_buildTargetDescriptors), {}); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(TargetDescriptorCompilerTestFixture, ValidProductionTargetsAndTestTargetMetas_ExpectValidProductionAndTestTargets) + { + // Given a valid build target descriptor list and a valid test target meta map + // When attempting to construct the test target + const auto& [productionTargetDescriptors, testTargetDescriptors] = + TestImpact::CompileTargetDescriptors(AZStd::move(m_buildTargetDescriptors), AZStd::move(m_testTargetMetaMap)); + + // Expect the production targets to match the expected targets + EXPECT_TRUE(productionTargetDescriptors.size() == 3); + EXPECT_TRUE(productionTargetDescriptors[0] == ConstructProductionTargetDescriptor("ProductionTargetA")); + EXPECT_TRUE(productionTargetDescriptors[1] == ConstructProductionTargetDescriptor("ProductionTargetB")); + EXPECT_TRUE(productionTargetDescriptors[2] == ConstructProductionTargetDescriptor("ProductionTargetC")); + + // Expect the test targets to match the expected targets + EXPECT_TRUE(testTargetDescriptors.size() == 2); + EXPECT_TRUE(testTargetDescriptors[0] == ConstructTestTargetDescriptor("TestTargetA", TestImpact::LaunchMethod::TestRunner)); + EXPECT_TRUE(testTargetDescriptors[1] == ConstructTestTargetDescriptor("TestTargetB", TestImpact::LaunchMethod::StandAlone)); + } + +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTestEnumerationSuiteFactoryTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTestEnumerationSuiteFactoryTest.cpp new file mode 100644 index 0000000000..d6e1dc5920 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTestEnumerationSuiteFactoryTest.cpp @@ -0,0 +1,589 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + TEST(GTestEnumerationSuiteFactory, ParseEmptyString_ThrowsArtifactException) + { + // Given an empty string + const AZStd::string rawEnum; + + try + { + // When attempting to parse the empty suite + const AZStd::vector suites = TestImpact::GTest::TestEnumerationSuitesFactory(rawEnum); + + // Do not expect the parsing to succeed + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(GTestEnumerationSuiteFactory, ParseInvalidString_ThrowsArtifactException) + { + // Given an invalid string + const AZStd::string rawEnum = "!@?"; + + try + { + // When attempting to parse the empty suite + const AZStd::vector suites = TestImpact::GTest::TestEnumerationSuitesFactory(rawEnum); + + // Do not expect the parsing to succeed + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(GTestEnumerationSuiteFactory, ParseTestTargetA_ReturnsValidSuitesAndTests) + { + const AZStd::vector expectedSuites = GetTestTargetATestEnumerationSuites(); + + // Given the raw enum output of TestTargetA + const AZStd::string rawEnum = + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n"; + + // When the raw enumeration text is parsed + const AZStd::vector suites = TestImpact::GTest::TestEnumerationSuitesFactory(rawEnum); + + // Expect the generated suite data to match that of the raw enum text + EXPECT_TRUE(suites == expectedSuites); + } + + TEST(GTestEnumerationSuiteFactory, ParseTestTargetB_ReturnsValidSuitesAndTests) + { + const AZStd::vector expectedSuites = GetTestTargetBTestEnumerationSuites(); + + // Given the raw enum output of TestTargetB + const AZStd::string rawEnum = + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n"; + + // When the raw enumeration text is parsed + const AZStd::vector suites = TestImpact::GTest::TestEnumerationSuitesFactory(rawEnum); + + // Expect the generated suite data to match that of the raw enum text + EXPECT_TRUE(suites == expectedSuites); + } + + TEST(GTestEnumerationSuiteFactory, ParseTestTargetC_ReturnsValidSuitesAndTests) + { + const AZStd::vector expectedSuites = GetTestTargetCTestEnumerationSuites(); + + // Given the raw enum output of TestTargetC + const AZStd::string rawEnum = + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n"; + + // When the raw enumeration text is parsed + const AZStd::vector suites = TestImpact::GTest::TestEnumerationSuitesFactory(rawEnum); + + // Expect the generated suite data to match that of the raw enum text + EXPECT_TRUE(suites == expectedSuites); + } + + TEST(GTestEnumerationSuiteFactory, ParseTestTargetD_ReturnsValidSuitesAndTests) + { + const AZStd::vector expectedSuites = GetTestTargetDTestEnumerationSuites(); + + // Given the raw enum output of TestTargetD + const AZStd::string rawEnum = + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n"; + + // When the raw enumeration text is parsed + const AZStd::vector suites = TestImpact::GTest::TestEnumerationSuitesFactory(rawEnum); + + // Expect the generated suite data to match that of the raw enum text + EXPECT_TRUE(suites == expectedSuites); + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTestRunSuiteFactoryTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTestRunSuiteFactoryTest.cpp new file mode 100644 index 0000000000..0f0a7dfdd8 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTestRunSuiteFactoryTest.cpp @@ -0,0 +1,592 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + TEST(GTestRunSuiteFactory, ParseEmptyString_ThrowsArtifactException) + { + // Given an empty string + const AZStd::string rawRun; + + try + { + // When attempting to parse the empty suite + const AZStd::vector suites = TestImpact::GTest::TestRunSuitesFactory(rawRun); + + // Do not expect the parsing to succeed + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect a artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(GTestRunSuiteFactory, ParseInvalidString_ThrowsArtifactException) + { + // Given an invalid string + const AZStd::string rawRun = "!@?"; + + try + { + // When attempting to parse the invalid suite + const AZStd::vector suites = TestImpact::GTest::TestRunSuitesFactory(rawRun); + + // Do not expect the parsing to succeed + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect a artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(GTestRunSuiteFactory, ParseTestTargetA_ReturnsValidSuitesAndTests) + { + const AZStd::vector expectedSuites = GetTestTargetATestRunSuites(); + + // Given the raw run output of TestTargetA + const AZStd::string rawRun = + "" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + + // When the raw run text is parsed + const AZStd::vector suites = TestImpact::GTest::TestRunSuitesFactory(rawRun); + + // Expect the generated suite data to match that of the raw run text + EXPECT_TRUE(suites == expectedSuites); + } + + TEST(GTestRunSuiteFactory, ParseTestTargetB_ReturnsValidSuitesAndTests) + { + const AZStd::vector expectedSuites = GetTestTargetBTestRunSuites(); + + // Given the raw run output of TestTargetB + const AZStd::string rawRun = + "" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + + // When the raw run text is parsed + const AZStd::vector suites = TestImpact::GTest::TestRunSuitesFactory(rawRun); + + // Expect the generated suite data to match that of the raw run text + EXPECT_TRUE(suites == expectedSuites); + } + + TEST(GTestRunSuiteFactory, ParseTestTargetC_ReturnsValidSuitesAndTests) + { + const AZStd::vector expectedSuites = GetTestTargetCTestRunSuites(); + + // Given the raw run output of TestTargetC + const AZStd::string rawRun = + "" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + + // When the raw run text is parsed + const AZStd::vector suites = TestImpact::GTest::TestRunSuitesFactory(rawRun); + + // Expect the generated suite data to match that of the raw run text + EXPECT_TRUE(suites == expectedSuites); + } + + TEST(GTestRunSuiteFactory, ParseTestTargetD_ReturnsValidSuitesAndTests) + { + const AZStd::vector expectedSuites = GetTestTargetDTestRunSuites(); + + // Given the raw run output of TestTargetD + const AZStd::string rawRun = + "" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; + + // When the raw run text is parsed + const AZStd::vector suites = TestImpact::GTest::TestRunSuitesFactory(rawRun); + + // Expect the generated suite data to match that of the raw run text + EXPECT_TRUE(suites == expectedSuites); + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTestTargetMetaMapFactoryTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTestTargetMetaMapFactoryTest.cpp new file mode 100644 index 0000000000..588c46d1ce --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Artifact/TestImpactTestTargetMetaMapFactoryTest.cpp @@ -0,0 +1,239 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include + +#include +#include + +namespace UnitTest +{ + TEST(TestTargetMetaMapFactoryTest, NoRawData_ExpectArtifactException) + { + // Given an empty meta data string + const AZStd::string rawTestTargetMetaData; + + try + { + // When attempting to construct the test target + const TestImpact::TestTargetMetaMap testTargetMetaData = TestImpact::TestTargetMetaMapFactory(rawTestTargetMetaData); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(TestTargetMetaMapFactoryTest, InvalidRawData_ExpectArtifactException) + { + // Given a raw meta data string of invalid data + const AZStd::string rawTestTargetMetaData = "abcde"; + + try + { + // When attempting to construct the test target + const TestImpact::TestTargetMetaMap testTargetMetaData = TestImpact::TestTargetMetaMapFactory(rawTestTargetMetaData); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(TestTargetMetaMapFactoryTest, EmptyTestMetaArray_ExpectArtifactException) + { + // Given a raw meta data string of valid JSON that contains no tests + const AZStd::string rawTestTargetMetaData = + "{" + " \"google\": {" + " \"test\": {" + " \"tests\": [" + " ]" + " }" + " }" + "}"; + + try + { + // When attempting to construct the test target + const TestImpact::TestTargetMetaMap testTargetMetaData = TestImpact::TestTargetMetaMapFactory(rawTestTargetMetaData); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(TestTargetMetaMapFactoryTest, EmptyName_ExpectArtifactException) + { + // Given a raw meta data string with a test that has no name value + const AZStd::string rawTestTargetMetaData = + "{" + " \"google\": {" + " \"test\": {" + " \"tests\": [" + " { \"name\": \"\", \"namespace\": \"Legacy\", \"suite\": \"main\", \"launch_method\": \"test_runner\" }" + " ]" + " }" + " }" + "}"; + + try + { + // When attempting to construct the test target + const TestImpact::TestTargetMetaMap testTargetMetaData = TestImpact::TestTargetMetaMapFactory(rawTestTargetMetaData); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(TestTargetMetaMapFactoryTest, EmptyBuildType_ExpectArtifactException) + { + // Given a raw meta data string with a test that has no build type + const AZStd::string rawTestTargetMetaData = + "{" + " \"google\": {" + " \"test\": {" + " \"tests\": [" + " { \"name\": \"TestName\", \"namespace\": \"Legacy\", \"suite\": \"main\", \"launch_method\": \"\" }" + " ]" + " }" + " }" + "}"; + + try + { + // When attempting to construct the test target + const TestImpact::TestTargetMetaMap testTargetMetaData = TestImpact::TestTargetMetaMapFactory(rawTestTargetMetaData); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(TestTargetMetaMapFactoryTest, InvalidBuildType_ExpectArtifactException) + { + // Given a raw meta data string with a test that has an invalid build type + const AZStd::string rawTestTargetMetaData = + "{" + " \"google\": {" + " \"test\": {" + " \"tests\": [" + " { \"name\": \"TestName\", \"namespace\": \"Legacy\", \"suite\": \"main\", \"launch_method\": \"Unknown\" }" + " ]" + " }" + " }" + "}"; + + try + { + // When attempting to construct the test target + const TestImpact::TestTargetMetaMap testTargetMetaData = TestImpact::TestTargetMetaMapFactory(rawTestTargetMetaData); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an artifact exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(TestTargetMetaMapFactoryTest, ValidMetaData_ExpectValidTestMetaData) + { + // Given a raw meta data string valid test meta-data + const AZStd::string rawTestTargetMetaData = + "{" + " \"google\": {" + " \"test\": {" + " \"tests\": [" + " { \"name\": \"TestA\", \"namespace\": \"Legacy\", \"suite\": \"main\", \"launch_method\": \"test_runner\" }," + " { \"name\": \"TestB\", \"namespace\": \"\", \"suite\": \"main\", \"launch_method\": \"test_runner\" }," + " { \"name\": \"TestC\", \"namespace\": \"\", \"suite\": \"\", \"launch_method\": \"test_runner\" }," + " { \"name\": \"TestD\", \"namespace\": \"Legacy\", \"suite\": \"main\", \"launch_method\": \"stand_alone\" }" + " ]" + " }" + " }" + "}"; + + // When attempting to construct the test target + const auto testTargetMetaData = TestImpact::TestTargetMetaMapFactory(rawTestTargetMetaData); + + // Expect the constructed test meta-data to match that of the supplied raw data + EXPECT_EQ(testTargetMetaData.size(), 4); + EXPECT_TRUE(testTargetMetaData.find("TestA") != testTargetMetaData.end()); + EXPECT_TRUE((testTargetMetaData.at("TestA") == TestImpact::TestTargetMeta{"main", TestImpact::LaunchMethod::TestRunner})); + EXPECT_TRUE(testTargetMetaData.find("TestB") != testTargetMetaData.end()); + EXPECT_TRUE((testTargetMetaData.at("TestB") == TestImpact::TestTargetMeta{"main", TestImpact::LaunchMethod::TestRunner})); + EXPECT_TRUE(testTargetMetaData.find("TestC") != testTargetMetaData.end()); + EXPECT_TRUE((testTargetMetaData.at("TestC") == TestImpact::TestTargetMeta{"", TestImpact::LaunchMethod::TestRunner})); + EXPECT_TRUE(testTargetMetaData.find("TestD") != testTargetMetaData.end()); + EXPECT_TRUE((testTargetMetaData.at("TestD") == TestImpact::TestTargetMeta{"main", TestImpact::LaunchMethod::StandAlone})); + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Process/TestImpactProcessSchedulerTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Process/TestImpactProcessSchedulerTest.cpp new file mode 100644 index 0000000000..c64b05e431 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Process/TestImpactProcessSchedulerTest.cpp @@ -0,0 +1,718 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace UnitTest +{ + using SchedulerPermutation = AZStd::tuple + < + size_t, // Number of concurrent processes + size_t // Number of processes to launch + >; + + class ProcessSchedulerTestFixtureWithParams + : public AllocatorsTestFixture + , private AZ::Debug::TraceMessageBus::Handler + , public ::testing::WithParamInterface + { + private: + bool OnOutput(const char*, const char*) override; + void SetUp() override; + void TearDown() override; + + protected: + void ConfigurePermutation(); + void ExpectSuccessfulLaunch(TestImpact::ProcessId pid); + void ExpectGracefulExit(TestImpact::ProcessId pid); + void ExpectUnsuccessfulLaunch(TestImpact::ProcessId pid); + void ExpectExitCondition( + TestImpact::ProcessId pid, AZStd::optional expectedExitCondition = AZStd::nullopt); + void DoNotExpectExitCondition(TestImpact::ProcessId pid); + void ExpectTerminatedProcess(TestImpact::ProcessId pid); + void ExpectTimeoutProcess(TestImpact::ProcessId pid); + void ExpectUnlaunchedProcess(TestImpact::ProcessId pid); + void ExpectLargeStdOutput(TestImpact::ProcessId pid); + void ExpectLargeStdError(TestImpact::ProcessId pid); + void ExpectSmallStdOutput(TestImpact::ProcessId pid); + void ExpectSmallStdError(TestImpact::ProcessId pid); + void DoNotExpectStdOutput(TestImpact::ProcessId pid); + void DoNotExpectStdError(TestImpact::ProcessId pid); + + struct ProcessResult + { + AZStd::optional m_launchResult; + AZStd::optional m_exitStatus; + AZStd::optional m_createTime; + AZStd::optional m_exitTime; + AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds(0); + AZStd::optional m_returnCode; + TestImpact::StdContent m_std; + }; + + size_t m_numMaxConcurrentProcesses = 0; + size_t m_numProcessesToLaunch = 0; + + AZStd::unique_ptr m_processScheduler; + TestImpact::ProcessLaunchCallback m_processLaunchCallback; + TestImpact::ProcessExitCallback m_processExitCallback; + AZStd::vector m_processInfos; + AZStd::vector m_processResults; + }; + + // Permutation values for small process batches + const std::vector SmallNumMaxConcurrentProcesses = {1, 4, 8}; + const std::vector SmallNumProcessesToLaunch = {1, 8, 32}; + + // Permutation values for large process batches + const std::vector LargeNumMaxConcurrentProcesses = {16, 32, 64}; + const std::vector LargeNumProcessesToLaunch = {128, 256, 512}; + + void ProcessSchedulerTestFixtureWithParams::ConfigurePermutation() + { + const auto& [numMaxConcurrentProcesses, numProcessesToLaunch] = GetParam(); + m_numMaxConcurrentProcesses = numMaxConcurrentProcesses; + m_numProcessesToLaunch = numProcessesToLaunch; + } + + // Consume AZ log spam + bool ProcessSchedulerTestFixtureWithParams::OnOutput([[maybe_unused]] const char*, [[maybe_unused]] const char*) + { + return true; + } + + void ProcessSchedulerTestFixtureWithParams::SetUp() + { + UnitTest::AllocatorsTestFixture::SetUp(); + AZ::Debug::TraceMessageBus::Handler::BusConnect(); + ConfigurePermutation(); + + m_processLaunchCallback = [this]( + TestImpact::ProcessId pid, + TestImpact::LaunchResult launchResult, + AZStd::chrono::high_resolution_clock::time_point createTime) + { + m_processResults[pid].m_launchResult = launchResult; + m_processResults[pid].m_createTime.emplace(createTime); + return TestImpact::CallbackResult::Continue; + }; + + m_processExitCallback = [this]( + TestImpact::ProcessId pid, + TestImpact::ExitCondition exitStatus, + TestImpact::ReturnCode returnCode, + TestImpact::StdContent&& std, + AZStd::chrono::high_resolution_clock::time_point exitTime) + { + m_processResults[pid].m_std = std::move(std); + m_processResults[pid].m_returnCode.emplace(returnCode); + m_processResults[pid].m_exitStatus = exitStatus; + m_processResults[pid].m_exitTime = exitTime; + m_processResults[pid].m_duration = + AZStd::chrono::duration_cast(exitTime - *m_processResults[pid].m_createTime); + return TestImpact::CallbackResult::Continue; + }; + + m_processResults.resize(m_numProcessesToLaunch); + } + + void ProcessSchedulerTestFixtureWithParams::TearDown() + { + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + UnitTest::AllocatorsTestFixture::TearDown(); + } + + // Expects the process to have exited under the specified circumstances + void ProcessSchedulerTestFixtureWithParams::ExpectExitCondition( + TestImpact::ProcessId pid, AZStd::optional expectedExitCondition) + { + const auto& [launchResult, exitStatus, createTime, exitTime, duration, returnCode, std] = m_processResults[pid]; + + // Expect the processes to have exited + EXPECT_TRUE(exitStatus.has_value()); + + // Expect the return code to be valid + EXPECT_TRUE(returnCode.has_value()); + + if (expectedExitCondition.has_value()) + { + // Expect the process to have exited under the specified conditions + EXPECT_EQ(exitStatus, expectedExitCondition); + + // Expect the return code to be that of the process's is (for gracefull exists) or that of the error code + // associated with the abnormal exit condition + EXPECT_EQ(returnCode, + expectedExitCondition == TestImpact::ExitCondition::Gracefull + ? pid + : static_cast(*exitStatus)); + } + + // Expect the duration to be non zero and the create time and exit time to have values as the processes has been in-flight + EXPECT_GT(duration.count(), 0); + EXPECT_TRUE(createTime.has_value()); + EXPECT_TRUE(exitTime.has_value()); + + // Expect the exit time to be later than the start time + EXPECT_GT(exitTime, createTime); + } + + // Expects the process to not have exited for to lack of being in-flight + void ProcessSchedulerTestFixtureWithParams::DoNotExpectExitCondition(TestImpact::ProcessId pid) + { + const auto& [launchResult, exitStatus, createTime, exitTime, duration, returnCode, std] = m_processResults[pid]; + + // Expect the processes to not have exited as the process has not been launched successfully + EXPECT_FALSE(exitStatus.has_value()); + + // Expect the return code to be invalid + EXPECT_FALSE(returnCode.has_value()); + + // Expect the duration to be zero as the process has not been in-flight + EXPECT_EQ(duration.count(), 0); + + // Do not expect the exit time to have a value + EXPECT_FALSE(exitTime.has_value()); + } + + // Expects the process to have been launched successfully, makes no assumptions about how it exited + void ProcessSchedulerTestFixtureWithParams::ExpectSuccessfulLaunch(TestImpact::ProcessId pid) + { + const auto& [launchResult, exitStatus, createTime, exitTime, duration, returnCode, std] = m_processResults[pid]; + + // Expect a launch to have been attempted by this process (not still in queue) + EXPECT_TRUE(launchResult.has_value()); + + // Expect the process to have launched successfully + EXPECT_EQ(launchResult, TestImpact::LaunchResult::Success); + } + + // Expects the process to have failed to launch + void ProcessSchedulerTestFixtureWithParams::ExpectUnsuccessfulLaunch(TestImpact::ProcessId pid) + { + const auto& [launchResult, exitStatus, createTime, exitTime, duration, returnCode, std] = m_processResults[pid]; + + // Expect a launch to have been attempted by this process (not still in queue) + EXPECT_TRUE(launchResult.has_value()); + + // Expect the process to have launched unsuccessfully + EXPECT_EQ(launchResult, TestImpact::LaunchResult::Failure); + + // Expect the create time to have a value as the process was technically created + EXPECT_TRUE(createTime.has_value()); + + DoNotExpectExitCondition(pid); + } + + // Expects the process to have exited gracefully of its own accord (i.e. not terminated for any reason by the scheduler) + void ProcessSchedulerTestFixtureWithParams::ExpectGracefulExit(TestImpact::ProcessId pid) + { + const auto& [launchResult, exitStatus, createTime, exitTime, duration, returnCode, std] = m_processResults[pid]; + + ExpectSuccessfulLaunch(pid); + ExpectExitCondition(pid, TestImpact::ExitCondition::Gracefull); + } + + // Expects the process to have been terminated by the client or scheduler + void ProcessSchedulerTestFixtureWithParams::ExpectTerminatedProcess(TestImpact::ProcessId pid) + { + const auto& [launchResult, exitStatus, createTime, exitTime, duration, returnCode, std] = m_processResults[pid]; + + ExpectSuccessfulLaunch(pid); + ExpectExitCondition(pid, TestImpact::ExitCondition::Terminated); + } + + // Expects the process to have been terminated by the scheduler due to the process or scheduler timing out + void ProcessSchedulerTestFixtureWithParams::ExpectTimeoutProcess(TestImpact::ProcessId pid) + { + const auto& [launchResult, exitStatus, createTime, exitTime, duration, returnCode, std] = m_processResults[pid]; + + ExpectSuccessfulLaunch(pid); + ExpectExitCondition(pid, TestImpact::ExitCondition::Timeout); + } + + // Expects the process to have not been attempted to launch (was still in the queue) + void ProcessSchedulerTestFixtureWithParams::ExpectUnlaunchedProcess(TestImpact::ProcessId pid) + { + const auto& [launchResult, exitStatus, createTime, exitTime, duration, returnCode, std] = m_processResults[pid]; + + // Expect a launch to not have been attempted by this process (still in queue) + EXPECT_FALSE(launchResult.has_value()); + + // Expect the create time to have a value as the process was technically created + EXPECT_FALSE(createTime.has_value()); + + DoNotExpectExitCondition(pid); + } + + // Expects the std output to have been captured from the process with a large volume of text + void ProcessSchedulerTestFixtureWithParams::ExpectLargeStdOutput(TestImpact::ProcessId pid) + { + const auto& stdOut = m_processResults[pid].m_std.m_out; + + // Expect standard output to have a value + EXPECT_TRUE(stdOut.has_value()); + + // Expect the output length to be that of the large text output from the child process + EXPECT_EQ(stdOut.value().length(), LargeTextSize); + } + + // Expects the std error to have been captured from the process with a large volume of text + void ProcessSchedulerTestFixtureWithParams::ExpectLargeStdError(TestImpact::ProcessId pid) + { + const auto& stdErr = m_processResults[pid].m_std.m_err; + + // Expect standard error to have a value + EXPECT_TRUE(stdErr.has_value()); + + // Expect the error length to be that of the large text output from the child process + EXPECT_EQ(stdErr.value().length(), LargeTextSize); + } + + // Expects the std output to have been captured from the process with a small known text string + void ProcessSchedulerTestFixtureWithParams::ExpectSmallStdOutput(TestImpact::ProcessId pid) + { + const auto& stdOut = m_processResults[pid].m_std.m_out; + + // Expect standard output to have a value + EXPECT_TRUE(stdOut.has_value()); + + // Expect the output to match the known stdout of the child + EXPECT_EQ(stdOut.value(), KnownTestProcessOutputString(pid)); + } + + // Expects the std error to have been captured from the process with a small known text string + void ProcessSchedulerTestFixtureWithParams::ExpectSmallStdError(TestImpact::ProcessId pid) + { + const auto& stdErr = m_processResults[pid].m_std.m_err; + + // Expect standard error to have a value + EXPECT_TRUE(stdErr.has_value()); + + // Expect the output to match the known stderr of the child + EXPECT_EQ(stdErr.value(), KnownTestProcessErrorString(pid)); + } + + // Expects no standard output from the child process + void ProcessSchedulerTestFixtureWithParams::DoNotExpectStdOutput(TestImpact::ProcessId pid) + { + const auto& stdOut = m_processResults[pid].m_std.m_out; + + // Do not expect standard output to have a value + EXPECT_FALSE(stdOut.has_value()); + } + + // Expects no standard error from the child process + void ProcessSchedulerTestFixtureWithParams::DoNotExpectStdError(TestImpact::ProcessId pid) + { + const auto& stdErr = m_processResults[pid].m_std.m_err; + + // Do not expect standard error to have a value + EXPECT_FALSE(stdErr.has_value()); + } + + TEST_P(ProcessSchedulerTestFixtureWithParams, ValidProcesses_SuccessfulLaunches) + { + // Given a set of processes to launch with valid arguments + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + m_processInfos.emplace_back( + pid, + ValidProcessPath, + ConstructTestProcessArgs(pid, NoSleep)); + } + + // When the process scheduler launches the processes + m_processScheduler = AZStd::make_unique( + m_processInfos, + m_processLaunchCallback, + m_processExitCallback, + m_numMaxConcurrentProcesses, + AZStd::nullopt, + AZStd::nullopt + ); + + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + ExpectGracefulExit(pid); + } + } + + TEST_P(ProcessSchedulerTestFixtureWithParams, ValidAndInvalidProcesses_LaunchSuccessesAndFailures) + { + const size_t invalidProcessGroup = 4; + + // Given a mixture of processes to launch with valid and invalid arguments + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + if (pid % invalidProcessGroup == 0) + { + m_processInfos.emplace_back( + pid, + InvalidProcessPath, + ConstructTestProcessArgs(pid, NoSleep)); + } + else + { + m_processInfos.emplace_back( + pid, + ValidProcessPath, + ConstructTestProcessArgs(pid, NoSleep)); + } + } + + // When the process scheduler launches the processes + m_processScheduler = AZStd::make_unique( + m_processInfos, + m_processLaunchCallback, + m_processExitCallback, + m_numMaxConcurrentProcesses, + AZStd::nullopt, + AZStd::nullopt + ); + + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + if (pid % invalidProcessGroup == 0) + { + ExpectUnsuccessfulLaunch(pid); + } + else + { + ExpectGracefulExit(pid); + } + + DoNotExpectStdOutput(pid); + DoNotExpectStdError(pid); + } + } + + TEST_P(ProcessSchedulerTestFixtureWithParams, ProcessTimeouts_InFlightProcessesTimeout) + { + const size_t timeoutProcessGroup = 4; + + // Given a mixture of processes to launch with some processes sleeping indefinately + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + if (pid % timeoutProcessGroup == 0) + { + m_processInfos.emplace_back(pid, ValidProcessPath, ConstructTestProcessArgs(pid, LongSleep)); + } + else + { + m_processInfos.emplace_back(pid, ValidProcessPath, ConstructTestProcessArgs(pid, NoSleep)); + } + } + + // When the process scheduler launches the processes with a process timeout value + m_processScheduler = AZStd::make_unique( + m_processInfos, + m_processLaunchCallback, + m_processExitCallback, + m_numMaxConcurrentProcesses, + AZStd::chrono::milliseconds(100), + AZStd::nullopt + ); + + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + if (pid % timeoutProcessGroup == 0) + { + ExpectTimeoutProcess(pid); + } + else + { + ExpectExitCondition(pid); + } + + DoNotExpectStdOutput(pid); + DoNotExpectStdError(pid); + } + } + + TEST_P( + ProcessSchedulerTestFixtureWithParams, ProcessLaunchCallbackAbort_InFlightProcessesTerminatedAndQueuedProcessesUnlaunched) + { + const size_t processToAbort = 8; + + // Given a set of processes to launch + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + m_processInfos.emplace_back( + pid, + ValidProcessPath, + ConstructTestProcessArgs(pid, NoSleep)); + } + + // Given a launch callback that will return the abort value for the process to abort + const auto abortingLaunchCallback = [this, processToAbort]( + TestImpact::ProcessId pid, + TestImpact::LaunchResult launchResult, + AZStd::chrono::high_resolution_clock::time_point createTime) + { + m_processLaunchCallback(pid, launchResult, createTime); + + if (pid == processToAbort) + { + return TestImpact::CallbackResult::Abort; + } + else + { + return TestImpact::CallbackResult::Continue; + } + }; + + // When the process scheduler launches the processes + m_processScheduler = AZStd::make_unique( + m_processInfos, + abortingLaunchCallback, + m_processExitCallback, + m_numMaxConcurrentProcesses, + AZStd::nullopt, + AZStd::nullopt + ); + + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + if (pid < processToAbort) + { + ExpectSuccessfulLaunch(pid); + } + else if (pid == processToAbort) + { + ExpectTerminatedProcess(pid); + } + else + { + ExpectUnlaunchedProcess(pid); + } + + DoNotExpectStdOutput(pid); + DoNotExpectStdError(pid); + } + } + + TEST_P(ProcessSchedulerTestFixtureWithParams, ProcessExitCallbackAbort_InFlightProcessesTerminated) + { + const size_t processToAbort = 4; + + // Given a set of processes to launch + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + m_processInfos.emplace_back( + pid, + ValidProcessPath, + ConstructTestProcessArgs(pid, NoSleep)); + } + + // Given an exit callback that will return the abort value for the process to abort + const auto abortingExitCallback = [this, processToAbort]( + TestImpact::ProcessId pid, + TestImpact::ExitCondition exitStatus, + TestImpact::ReturnCode returnCode, + TestImpact::StdContent&& std, + AZStd::chrono::high_resolution_clock::time_point exitTime) + { + m_processExitCallback(pid, exitStatus, returnCode, std::move(std), exitTime); + + if (pid == processToAbort) + { + return TestImpact::CallbackResult::Abort; + } + else + { + return TestImpact::CallbackResult::Continue; + } + }; + + // When the process scheduler launches the processes + m_processScheduler = AZStd::make_unique( + m_processInfos, + m_processLaunchCallback, + abortingExitCallback, + m_numMaxConcurrentProcesses, + AZStd::nullopt, + AZStd::nullopt + ); + + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + if (pid < processToAbort) + { + ExpectSuccessfulLaunch(pid); + } + else if (pid == processToAbort) + { + ExpectGracefulExit(pid); + } + else + { + if (m_processResults[pid].m_launchResult.has_value()) + { + ExpectSuccessfulLaunch(pid); + } + else + { + ExpectUnlaunchedProcess(pid); + } + } + + DoNotExpectStdOutput(pid); + DoNotExpectStdError(pid); + } + } + + TEST_P(ProcessSchedulerTestFixtureWithParams, SchedulerTimeout_QueuedAndInFlightProcessesTerminated) + { + const size_t processToTimeout = 0; + + // Given a set of processes to launch where one process will sleep indefinately + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + if (pid == processToTimeout) + { + m_processInfos.emplace_back( + pid, + ValidProcessPath, + ConstructTestProcessArgs(pid, LongSleep)); + } + else + { + m_processInfos.emplace_back( + pid, + ValidProcessPath, + ConstructTestProcessArgs(pid, NoSleep)); + } + } + + // When the process scheduler launches the processes with a scheduler timeout value + m_processScheduler = AZStd::make_unique( + m_processInfos, + m_processLaunchCallback, + m_processExitCallback, + m_numMaxConcurrentProcesses, + AZStd::nullopt, + AZStd::chrono::milliseconds(100) + ); + + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + if (pid == processToTimeout) + { + ExpectTimeoutProcess(pid); + } + else + { + if (m_processResults[pid].m_launchResult.has_value()) + { + ExpectSuccessfulLaunch(pid); + } + else + { + ExpectUnlaunchedProcess(pid); + } + } + + DoNotExpectStdOutput(pid); + DoNotExpectStdError(pid); + } + } + + TEST_P(ProcessSchedulerTestFixtureWithParams, RedirectStdOut_StdOutputIsLargeTextString) + { + // Given a set of processes to launch + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + m_processInfos.emplace_back( + pid, + TestImpact::StdOutputRouting::ToParent, + TestImpact::StdErrorRouting::None, + ValidProcessPath, + ConstructTestProcessArgsLargeText(pid, NoSleep)); + } + + // When the process scheduler launches the processes + m_processScheduler = AZStd::make_unique( + m_processInfos, + m_processLaunchCallback, + m_processExitCallback, + m_numMaxConcurrentProcesses, + AZStd::nullopt, + AZStd::nullopt + ); + + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + ExpectGracefulExit(pid); + ExpectLargeStdOutput(pid); + DoNotExpectStdError(pid); + } + } + + TEST_P(ProcessSchedulerTestFixtureWithParams, RedirectStdError_StdErrorIsLargeTextString) + { + // Given a set of processes to launch + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + m_processInfos.emplace_back( + pid, + TestImpact::StdOutputRouting::None, + TestImpact::StdErrorRouting::ToParent, + ValidProcessPath, + ConstructTestProcessArgsLargeText(pid, NoSleep)); + } + + // When the process scheduler launches the processes + m_processScheduler = AZStd::make_unique( + m_processInfos, + m_processLaunchCallback, + m_processExitCallback, + m_numMaxConcurrentProcesses, + AZStd::nullopt, + AZStd::nullopt + ); + + for (size_t pid = 0; pid < m_numProcessesToLaunch; pid++) + { + ExpectGracefulExit(pid); + DoNotExpectStdOutput(pid); + ExpectLargeStdError(pid); + } + } + + INSTANTIATE_TEST_CASE_P( + SmallConcurrentProcesses, + ProcessSchedulerTestFixtureWithParams, + ::testing::Combine( + ::testing::ValuesIn(SmallNumMaxConcurrentProcesses), + ::testing::ValuesIn(SmallNumProcessesToLaunch) + )); + + INSTANTIATE_TEST_CASE_P( + LargeConcurrentProcesses, + ProcessSchedulerTestFixtureWithParams, + ::testing::Combine( + ::testing::ValuesIn(LargeNumMaxConcurrentProcesses), + ::testing::ValuesIn(LargeNumProcessesToLaunch) + )); +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Process/TestImpactProcessTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Process/TestImpactProcessTest.cpp new file mode 100644 index 0000000000..df4ea5a967 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Process/TestImpactProcessTest.cpp @@ -0,0 +1,491 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include + +#include +#include +#include + +namespace UnitTest +{ + class ProcessTestFixture + : public AllocatorsTestFixture + { + protected: + void SetUp() override + { + UnitTest::AllocatorsTestFixture::SetUp(); + } + + void TearDown() override + { + if (m_process && m_process->IsRunning()) + { + m_process->Terminate(0); + } + + UnitTest::AllocatorsTestFixture::TearDown(); + } + + AZStd::unique_ptr m_process; + static constexpr const TestImpact::ProcessId m_id = 1; + static constexpr const TestImpact::ReturnCode m_terminateErrorCode = 666; + }; + + TEST_F(ProcessTestFixture, LaunchValidProcess_ProcessReturnsSuccessfully) + { + // Given a process launched with a valid binary + TestImpact::ProcessInfo processInfo(m_id, ValidProcessPath); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process has exited + m_process->BlockUntilExit(); + + // Expect the process to no longer be running + EXPECT_FALSE(m_process->IsRunning()); + } + + TEST_F(ProcessTestFixture, LaunchInvalidProcessInfo_ThrowsProcessException) + { + try + { + // Given a process launched with an invalid binary + TestImpact::ProcessInfo processInfo(m_id, ""); + + // Do not expect this code to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ProcessException& e) + { + // Except a process exception to be thrown + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(ProcessTestFixture, LaunchInvalidBinary_ThrowsProcessException) + { + try + { + // Given a process launched with an ivalid binary + TestImpact::ProcessInfo processInfo(m_id, "#!#zz:/z/z/z.exe.z@"); + + // When the process is attempted launch + m_process = TestImpact::LaunchProcess(processInfo); + + // Do not expect this code to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ProcessException& e) + { + // Except a process exception to be thrown + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(ProcessTestFixture, GetProcessInfo_ReturnsProcessInfo) + { + // Given a process launched with a valid binary and args + const AZStd::string args = ConstructTestProcessArgs(m_id, NoSleep); + TestImpact::ProcessInfo processInfo(m_id, ValidProcessPath, args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process has exited + m_process->BlockUntilExit(); + + // Expect the retrieved process info to match to process info used to launch the process + EXPECT_EQ(m_process->GetProcessInfo().GetId(), m_id); + EXPECT_EQ(m_process->GetProcessInfo().GetProcessPath(), ValidProcessPath); + EXPECT_EQ(m_process->GetProcessInfo().GetStartupArgs(), args); + } + + TEST_F(ProcessTestFixture, GetReturnCodeAfterExit_ReturnsArg) + { + // Given a process launched with a valid binary and args + const AZStd::string args = ConstructTestProcessArgs(m_id, NoSleep); + TestImpact::ProcessInfo processInfo(m_id, ValidProcessPath, args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process has exited + m_process->BlockUntilExit(); + + // Expect the process to no longer be running + EXPECT_FALSE(m_process->IsRunning()); + + // Expect the return value to be the process id + EXPECT_EQ(m_process->GetReturnCode(), m_id); + } + + TEST_F(ProcessTestFixture, GetReturnCodeInFlight_ReturnsNullOpt) + { + // Given a process launched with a valid binary and args to sleep for 100 seconds + const AZStd::string args = ConstructTestProcessArgs(m_id, LongSleep); + TestImpact::ProcessInfo processInfo(m_id, ValidProcessPath, args); + + // When the process is running + m_process = TestImpact::LaunchProcess(processInfo); + + // Expect the return value to be empty + EXPECT_EQ(m_process->GetReturnCode(), AZStd::nullopt); + } + + TEST_F(ProcessTestFixture, TerminateWithErrorCode_ReturnsErrorCode) + { + // Given a process launched with a valid binary and args to sleep for 100 seconds + const AZStd::string args = ConstructTestProcessArgs(m_id, LongSleep); + TestImpact::ProcessInfo processInfo(m_id, ValidProcessPath, args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process is terminated by the client with the specified error code + m_process->Terminate(m_terminateErrorCode); + + // Expect the return value to be the error code specified in the Terminate call + EXPECT_EQ(m_process->GetReturnCode(), m_terminateErrorCode); + + // Expect the process to no longer be running + EXPECT_FALSE(m_process->IsRunning()); + } + + TEST_F(ProcessTestFixture, CheckIsRunningWhilstRunning_ProcessIsRunning) + { + // Given a process launched with a valid binary and args to sleep for 100 seconds + const AZStd::string args = ConstructTestProcessArgs(m_id, LongSleep); + TestImpact::ProcessInfo processInfo(m_id, ValidProcessPath, args); + + // When the process is running + m_process = TestImpact::LaunchProcess(processInfo); + + // Expect the process to be running + EXPECT_TRUE(m_process->IsRunning()); + } + + TEST_F(ProcessTestFixture, CheckIsRunningWhilstNotRunning_ReturnsFalse) + { + // Given a process launched with a valid binary + TestImpact::ProcessInfo processInfo(m_id, ValidProcessPath); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process is terminated by the client + m_process->Terminate(0); + + // Expect the process to be running + EXPECT_FALSE(m_process->IsRunning()); + } + + TEST_F(ProcessTestFixture, RedirectStdOut_OutputIsKnownTestProcessOutputString) + { + // Given a process launched with a valid binary and standard output redirected to parent + const AZStd::string args = ConstructTestProcessArgs(m_id, NoSleep); + TestImpact::ProcessInfo processInfo( + m_id, + TestImpact::StdOutputRouting::ToParent, + TestImpact::StdErrorRouting::None, + ValidProcessPath, + args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process exits + m_process->BlockUntilExit(); + + // Expect stdoutput to be rerouted to the parent + EXPECT_TRUE(m_process->GetProcessInfo().ParentHasStdOutput()); + + // Do not expect stdouterr to be rerouted to the parent + EXPECT_FALSE(m_process->GetProcessInfo().ParentHasStdError()); + + // Expect the output to match the known stdout of the child + EXPECT_EQ(m_process->ConsumeStdOut(), KnownTestProcessOutputString(m_id)); + } + + TEST_F(ProcessTestFixture, RedirectStdErr_OutputIsKnownTestProcessOutputString) + { + // Given a process launched with a valid binary and standard error redirected to parent + const AZStd::string args = ConstructTestProcessArgs(m_id, NoSleep); + TestImpact::ProcessInfo processInfo( + m_id, + TestImpact::StdOutputRouting::None, + TestImpact::StdErrorRouting::ToParent, + ValidProcessPath, + args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process exits + m_process->BlockUntilExit(); + + // Do not expect stdoutput to be rerouted to the parent + EXPECT_FALSE(m_process->GetProcessInfo().ParentHasStdOutput()); + + // Expect stderror to be rerouted to the parent + EXPECT_TRUE(m_process->GetProcessInfo().ParentHasStdError()); + + // Expect the output to match the known stdout of the child + EXPECT_EQ(m_process->ConsumeStdErr(), KnownTestProcessErrorString(m_id)); + } + + TEST_F(ProcessTestFixture, RedirectStdOutAndTerminate_OutputIsEmpty) + { + // Given a process launched with a valid binary and standard output redirected to parent + const AZStd::string args = ConstructTestProcessArgs(m_id, NoSleep); + TestImpact::ProcessInfo processInfo( + m_id, + TestImpact::StdOutputRouting::ToParent, + TestImpact::StdErrorRouting::None, + ValidProcessPath, + args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process is terminated + m_process->Terminate(0); + + // Expect stdoutput to be rerouted to the parent + EXPECT_TRUE(m_process->GetProcessInfo().ParentHasStdOutput()); + + // Do not expect stdouterr to be rerouted to the parent + EXPECT_FALSE(m_process->GetProcessInfo().ParentHasStdError()); + + // Expect the output to be empty + EXPECT_FALSE(m_process->ConsumeStdOut().has_value()); + } + + TEST_F(ProcessTestFixture, RedirectStdErrAndTerminate_OutputIsEmpty) + { + // Given a process launched with a valid binary and standard error redirected to parent + const AZStd::string args = ConstructTestProcessArgs(m_id, NoSleep); + TestImpact::ProcessInfo processInfo( + m_id, + TestImpact::StdOutputRouting::None, + TestImpact::StdErrorRouting::ToParent, + ValidProcessPath, + args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process is terminated + m_process->Terminate(0); + + // Do not expect stdoutput to be rerouted to the parent + EXPECT_FALSE(m_process->GetProcessInfo().ParentHasStdOutput()); + + // Expect stderror to be rerouted to the parent + EXPECT_TRUE(m_process->GetProcessInfo().ParentHasStdError()); + + // Expect the output to be empty + EXPECT_FALSE(m_process->ConsumeStdErr().has_value()); + } + + TEST_F(ProcessTestFixture, RedirectStdOutAndStdErrorRouting_OutputsAreKnownTestProcessOutputStrings) + { + // Given a process launched with a valid binary and both standard output and standard error redirected to parent + const AZStd::string args = ConstructTestProcessArgs(m_id, NoSleep); + TestImpact::ProcessInfo processInfo( + m_id, + TestImpact::StdOutputRouting::ToParent, + TestImpact::StdErrorRouting::ToParent, + ValidProcessPath, + args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process exits + m_process->BlockUntilExit(); + + // Expect stdoutput to be rerouted to the parent + EXPECT_TRUE(m_process->GetProcessInfo().ParentHasStdOutput()); + + // Expect stderror to be rerouted to the parent + EXPECT_TRUE(m_process->GetProcessInfo().ParentHasStdError()); + + // Expect the output to match the known stdout and stderr of the child + EXPECT_EQ(m_process->ConsumeStdOut(), KnownTestProcessOutputString(m_id)); + EXPECT_EQ(m_process->ConsumeStdErr(), KnownTestProcessErrorString(m_id)); + } + + TEST_F(ProcessTestFixture, NoStdOutOrStdErrRedirect_OutputIsEmpty) + { + // Given a process launched with a valid binary and both standard output and standard error redirected to parent + const AZStd::string args = ConstructTestProcessArgs(m_id, NoSleep); + TestImpact::ProcessInfo processInfo( + m_id, + TestImpact::StdOutputRouting::None, + TestImpact::StdErrorRouting::None, + ValidProcessPath, + args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process exits + m_process->BlockUntilExit(); + + // Do not expect stdoutput to be rerouted to the parent + EXPECT_FALSE(m_process->GetProcessInfo().ParentHasStdOutput()); + + // Do not expect stdouterr to be rerouted to the parent + EXPECT_FALSE(m_process->GetProcessInfo().ParentHasStdError()); + + // Expect the output to to be empty + EXPECT_FALSE(m_process->ConsumeStdOut().has_value()); + EXPECT_FALSE(m_process->ConsumeStdErr().has_value()); + } + + TEST_F(ProcessTestFixture, LargePipeNoRedirects_OutputsAreEmpty) + { + // Given a process outputting large text with no standard output and no standard error redirected to parent + const AZStd::string args = ConstructTestProcessArgsLargeText(m_id, NoSleep); + TestImpact::ProcessInfo processInfo( + m_id, + TestImpact::StdOutputRouting::None, + TestImpact::StdErrorRouting::None, + ValidProcessPath, + args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process exits + m_process->BlockUntilExit(); + + // Expect the output to to be empty + EXPECT_FALSE(m_process->ConsumeStdOut().has_value()); + EXPECT_FALSE(m_process->ConsumeStdErr().has_value()); + } + + TEST_F(ProcessTestFixture, LargePipeNoRedirectsAndTerminated_OutputsAreEmpty) + { + // Given a process outputting large text with no standard output and standard error redirected to parent + const AZStd::string args = ConstructTestProcessArgsLargeText(m_id, LongSleep); + TestImpact::ProcessInfo processInfo( + m_id, + TestImpact::StdOutputRouting::None, + TestImpact::StdErrorRouting::None, + ValidProcessPath, + args); + + // When the process is running + m_process = TestImpact::LaunchProcess(processInfo); + + // Expect the output to to be empty + EXPECT_FALSE(m_process->ConsumeStdOut().has_value()); + EXPECT_FALSE(m_process->ConsumeStdErr().has_value()); + + // When the process is terminated + m_process->Terminate(0); + + // Expect the output to to be empty + EXPECT_FALSE(m_process->ConsumeStdOut().has_value()); + EXPECT_FALSE(m_process->ConsumeStdErr().has_value()); + } + + TEST_F(ProcessTestFixture, LargePipeRedirectsAndReadWhilstRunning_OutputsAreEmpty) + { + // Given a process outputting large text with no standard output and standard error redirected to parent + const AZStd::string args = ConstructTestProcessArgsLargeText(m_id, ShortSleep); + TestImpact::ProcessInfo processInfo( + m_id, + TestImpact::StdOutputRouting::None, + TestImpact::StdErrorRouting::None, + ValidProcessPath, + args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the outputs are read as the process is running + while (m_process->IsRunning()) + { + // Expect the outputs to be empty + EXPECT_FALSE(m_process->ConsumeStdOut().has_value()); + EXPECT_FALSE(m_process->ConsumeStdErr().has_value()); + } + } + + TEST_F(ProcessTestFixture, LargePipeRedirectsAndTerminated_OutputsAreEmpty) + { + // Given a process outputting large text with both standard output and standard error redirected to parent + const AZStd::string args = ConstructTestProcessArgsLargeText(m_id, LongSleep); + TestImpact::ProcessInfo processInfo( + m_id, + TestImpact::StdOutputRouting::ToParent, + TestImpact::StdErrorRouting::ToParent, + ValidProcessPath, + args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process is terminated + m_process->Terminate(0); + + // Expect the outputs to be empty + EXPECT_FALSE(m_process->ConsumeStdOut().has_value()); + EXPECT_FALSE(m_process->ConsumeStdErr().has_value()); + } + + TEST_F(ProcessTestFixture, LargePipeRedirectsAndBlockedUntilExit_OutputsAreLargeTextFileSize) + { + // Given a process outputting large text with both standard output and standard error redirected to parent + const AZStd::string args = ConstructTestProcessArgsLargeText(m_id, NoSleep); + TestImpact::ProcessInfo processInfo( + m_id, + TestImpact::StdOutputRouting::ToParent, + TestImpact::StdErrorRouting::ToParent, + ValidProcessPath, + args); + m_process = TestImpact::LaunchProcess(processInfo); + + // When the process exits + m_process->BlockUntilExit(); + + // Expect the output lengths to be that of the large text output from the child process + EXPECT_EQ(m_process->ConsumeStdOut().value().length(), LargeTextSize); + EXPECT_EQ(m_process->ConsumeStdErr().value().length(), LargeTextSize); + } + + TEST_F(ProcessTestFixture, LargePipeRedirectsAndReadWhilstRunning_TotalOutputsAreLargeTextFileSize) + { + // Given a process outputting large text with both standard output and standard error redirected to parent + const AZStd::string args = ConstructTestProcessArgsLargeText(m_id, NoSleep); + TestImpact::ProcessInfo processInfo( + m_id, + TestImpact::StdOutputRouting::ToParent, + TestImpact::StdErrorRouting::ToParent, + ValidProcessPath, args); + m_process = TestImpact::LaunchProcess(processInfo); + + size_t stdOutBytes = 0; + size_t stdErrBytes = 0; + while (m_process->IsRunning()) + { + // When the outputs are read as the process is running + if (auto output = m_process->ConsumeStdOut(); output.has_value()) + { + stdOutBytes += output.value().length(); + } + + if (auto output = m_process->ConsumeStdErr(); output.has_value()) + { + stdErrBytes += output.value().length(); + } + } + + // Expect the output lengths to be that of the large text output from the child process + EXPECT_EQ(stdOutBytes, LargeTextSize); + EXPECT_EQ(stdErrBytes, LargeTextSize); + + // Expect the outputs to be empty + EXPECT_FALSE(m_process->ConsumeStdOut().has_value()); + EXPECT_FALSE(m_process->ConsumeStdErr().has_value()); + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Target/TestImpactBuildTargetTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Target/TestImpactBuildTargetTest.cpp new file mode 100644 index 0000000000..e416d3836e --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Target/TestImpactBuildTargetTest.cpp @@ -0,0 +1,352 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include + +#include +#include + +namespace UnitTest +{ + namespace + { + AZStd::string GenerateBuildTargetName(size_t index) + { + return AZStd::string::format("Target%u", index); + } + + AZStd::string GenerateBuildTargetOutputName(size_t index) + { + return AZStd::string::format("Output%u", index); + } + + AZ::IO::Path GenerateBuildTargetPath(size_t index) + { + return AZStd::string::format("C:\\Repo\\Dir%u", index); + } + + AZStd::string GenerateTestTargetSuite(size_t index) + { + return AZStd::string::format("Suite%u", index); + } + + TestImpact::LaunchMethod GenerateLaunchMethod(size_t index) + { + return index % 2 == 0 ? TestImpact::LaunchMethod::StandAlone : TestImpact::LaunchMethod::TestRunner; + } + + AZStd::string GenerateStaticSourceFile(size_t index) + { + return AZStd::string::format("StaticSource%u", index); + } + + TestImpact::AutogenPairs GenerateAutogenSourceFiles(size_t index) + { + TestImpact::AutogenPairs autogen; + autogen.m_input = AZStd::string::format("InputSource%u", index); + autogen.m_outputs.push_back(AZStd::string::format("OutputSource%u", index)); + autogen.m_outputs.push_back(AZStd::string::format("OutputHeader%u", index)); + return autogen; + } + + TestImpact::TargetSources GenerateTargetSources(size_t index) + { + TestImpact::TargetSources sources; + sources.m_staticSources.resize(index + 1); + for (size_t i = 0; i < sources.m_staticSources.size(); i++) + { + sources.m_staticSources[i] = GenerateStaticSourceFile(i); + } + + // Only generate autogen sources for even-numbered indexes + if (index % 2 == 0) + { + sources.m_autogenSources.resize(index + 1); + for (size_t i = 0; i < sources.m_autogenSources.size(); i++) + { + sources.m_autogenSources[i] = GenerateAutogenSourceFiles(i); + } + } + + return sources; + } + + TestImpact::ProductionTargetDescriptor GenerateProductionTargetDescriptor(size_t index = 0) + { + return TestImpact::ProductionTargetDescriptor( + { + TestImpact::BuildMetaData + { + GenerateBuildTargetName(index), GenerateBuildTargetOutputName(index), GenerateBuildTargetPath(index) + }, + GenerateTargetSources(index) + }); + } + + TestImpact::TestTargetDescriptor GenerateTestTargetDescriptor(size_t index = 0) + { + return TestImpact::TestTargetDescriptor( + { + TestImpact::BuildMetaData + { + GenerateBuildTargetName(index), GenerateBuildTargetOutputName(index), GenerateBuildTargetPath(index) + }, + GenerateTargetSources(index) + }, + TestImpact::TestTargetMeta + { + GenerateTestTargetSuite(index), GenerateLaunchMethod(index) + }); + } + } // namespace + + template + typename Target::Descriptor GenerateTargetDescriptor(size_t index = 0) + { + static_assert( + AZStd::is_same_v || AZStd::is_same_v, + "Unexpected target type, wants TestImpact::ProductionTarget or TestImpact::TestTarget"); + + if constexpr (AZStd::is_same_v) + { + return GenerateProductionTargetDescriptor(index); + } + else + { + return GenerateTestTargetDescriptor(index); + } + } + + void ValidateSources(const TestImpact::TargetSources& sources, size_t index = 0) + { + EXPECT_EQ(sources.m_staticSources.size(), index + 1); + for (size_t i = 0; i < sources.m_staticSources.size(); i++) + { + EXPECT_EQ(sources.m_staticSources[i], GenerateStaticSourceFile(i)); + } + + // Even numbered indexes have autogen sources + if (index % 2 == 0) + { + EXPECT_EQ(sources.m_autogenSources.size(), index + 1); + for (size_t i = 0; i < sources.m_autogenSources.size(); i++) + { + enum + { + OutputSource = 0, + OutputHeader = 1 + }; + + const TestImpact::AutogenPairs expectedAutogenSources = GenerateAutogenSourceFiles(i); + EXPECT_EQ(sources.m_autogenSources[i].m_input, expectedAutogenSources.m_input); + EXPECT_EQ(sources.m_autogenSources[i].m_outputs[OutputSource], expectedAutogenSources.m_outputs[OutputSource]); + EXPECT_EQ(sources.m_autogenSources[i].m_outputs[OutputHeader], expectedAutogenSources.m_outputs[OutputHeader]); + } + } + else + { + EXPECT_TRUE(sources.m_autogenSources.empty()); + } + } + + void ValidateTarget(const TestImpact::ProductionTarget& target, size_t index = 0) + { + EXPECT_EQ(target.GetName(), GenerateBuildTargetName(index)); + EXPECT_EQ(target.GetOutputName(), GenerateBuildTargetOutputName(index)); + EXPECT_EQ(target.GetPath(), GenerateBuildTargetPath(index)); + EXPECT_EQ(target.GetType(), TestImpact::TargetType::Production); + ValidateSources(target.GetSources(), index); + } + + void ValidateTarget(const TestImpact::TestTarget& target, size_t index = 0) + { + EXPECT_EQ(target.GetName(), GenerateBuildTargetName(index)); + EXPECT_EQ(target.GetOutputName(), GenerateBuildTargetOutputName(index)); + EXPECT_EQ(target.GetPath(), GenerateBuildTargetPath(index)); + EXPECT_EQ(target.GetType(), TestImpact::TargetType::Test); + EXPECT_EQ(target.GetSuite(), GenerateTestTargetSuite(index)); + EXPECT_EQ(target.GetLaunchMethod(), GenerateLaunchMethod(index)); + ValidateSources(target.GetSources(), index); + } + + template + class TargetListFixture + : public AllocatorsTestFixture + { + public: + using TargetListType = TargetList; + using TargetType = typename TargetList::TargetType; + + static constexpr size_t m_numTargets = 10; + }; + + using TargetTypes = testing::Types; + TYPED_TEST_CASE(TargetListFixture, TargetTypes); + + TYPED_TEST(TargetListFixture, CreateTarget_ExpectValidTarget) + { + // Given a target of the specified type + TargetType target(GenerateTargetDescriptor()); + + // Expect the target to match the procedurally generated target descriptor + ValidateTarget(target); + } + + TYPED_TEST(TargetListFixture, CreateEmptyTargetList_ExpectTargetException) + { + try + { + // Given an empty target list + TargetListType targetList({}); + + // Do not expect the target list construction to succeed + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::TargetException& e) + { + // Expect a target exception to be thrown + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TYPED_TEST(TargetListFixture, CreateTargetListWithDuplicateDescriptor_ExpectTargetException) + { + try + { + // Given a set of target descriptors containing a single duplicate + AZStd::vector descriptors; + descriptors.reserve(m_numTargets); + for (size_t i = 0; i < m_numTargets; i++) + { + // Wrap the last index round to repeat the first index + descriptors.push_back(GenerateTargetDescriptor(i % (m_numTargets - 1))); + } + + // When constructing the target list containing the duplicate target descriptor + TargetListType targetList(AZStd::move(descriptors)); + + // Do not expect the target list construction to succeed + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::TargetException& e) + { + // Expect a target exception to be thrown + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TYPED_TEST(TargetListFixture, CreateTargetListWithValidDescriptors_ExpectValidTargetList) + { + // Given a valid set of target descriptor + AZStd::vector descriptors; + descriptors.reserve(m_numTargets); + for (size_t i = 0; i < m_numTargets; i++) + { + descriptors.push_back(GenerateTargetDescriptor(i)); + } + + // When constructing the target list containing the valid target descriptors + TargetListType targetList(AZStd::move(descriptors)); + + // Expect the number of targets in the list to match the number of target descriptors used to construct the list + EXPECT_EQ(targetList.GetNumTargets(), m_numTargets); + + for (size_t i = 0; i < targetList.GetNumTargets(); i++) + { + // Expect the target to match the procedurally generated target descriptor + ValidateTarget(targetList.GetTargets()[i], i); + + // Expect the target obtained by name to match the procedurally generated target descriptor + auto target = targetList.GetTarget(GenerateBuildTargetName(i)); + EXPECT_TRUE(target); + EXPECT_EQ(target->GetName(), GenerateBuildTargetName(i)); + } + } + + TYPED_TEST(TargetListFixture, FindNonExistantTargets_ExpectEmptyResults) + { + // Given a valid set of target descriptor + AZStd::vector descriptors; + descriptors.reserve(m_numTargets); + for (size_t i = 0; i < m_numTargets; i++) + { + descriptors.push_back(GenerateTargetDescriptor(i)); + } + + // When constructing the target list containing the valid target descriptors + TargetListType targetList(AZStd::move(descriptors)); + + // Expect the number of targets in the list to match the number of target descriptors used to construct the list + EXPECT_EQ(targetList.GetNumTargets(), m_numTargets); + + for (size_t i = 0; i < targetList.GetNumTargets(); i++) + { + // When attempting to find a target that does not exist + auto target = targetList.GetTarget(GenerateBuildTargetName(i + targetList.GetNumTargets())); + + // Expect an empty result + EXPECT_FALSE(target); + } + } + + TYPED_TEST(TargetListFixture, FindNonExistantTargetsAndThrow_ExpectTargetExceptionss) + { + // Given a valid set of target descriptor + AZStd::vector descriptors; + descriptors.reserve(m_numTargets); + for (size_t i = 0; i < m_numTargets; i++) + { + descriptors.push_back(GenerateTargetDescriptor(i)); + } + + // When constructing the target list containing the valid target descriptors + TargetListType targetList(AZStd::move(descriptors)); + + // Expect the number of targets in the list to match the number of target descriptors used to construct the list + EXPECT_EQ(targetList.GetNumTargets(), m_numTargets); + + for (size_t i = 0; i < targetList.GetNumTargets(); i++) + { + try + { + // When attempting to find a target that does not exist + auto target = targetList.GetTargetOrThrow(GenerateBuildTargetName(i + targetList.GetNumTargets())); + + // Do not expect the target list construction to succeed + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::TargetException& e) + { + // Expect a target exception to be thrown + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactInstrumentedTestRunnerTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactInstrumentedTestRunnerTest.cpp new file mode 100644 index 0000000000..897eda012c --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactInstrumentedTestRunnerTest.cpp @@ -0,0 +1,822 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + using JobExceptionPolicy = TestImpact::InstrumentedTestRunner::JobExceptionPolicy; + using CoverageExceptionPolicy = TestImpact::InstrumentedTestRunner::CoverageExceptionPolicy; + + struct TargetPaths + { + AZ::IO::Path m_targetBinary; + AZ::IO::Path m_testRunArtifact; + AZ::IO::Path m_testCoverageArtifact; + }; + + // Indices for looking up job command arguments for the different coverage levels + enum CoverageLevel : uint8_t + { + LineLevel = 0, + SourceLevel + }; + + // Get the job command for an instrumented test run + AZStd::string GetRunCommandForTarget(const TargetPaths& testTarget, CoverageLevel coverageLevel, const char* sourcesFilter) + { + AZStd::string args = AZStd::string::format( + "%s " // 1. Instrumented test runner + "--coverage_level %s " // 2. Coverage level + "--export_type cobertura:\"%s\" " // 3. Test coverage artifact path + "--modules \"%s\" " // 4. Modules path + "--excluded_modules \"%s\" " // 5. Exclude modules + "--sources \"%s\" -- " // 6. Sources path + "\"%s\" " // 7. Test runner binary + "\"%s\" " // 8. Test target bin + "AzRunUnitTests " + "--gtest_output=xml:%s", // 9. Test run result artifact + + LY_TEST_IMPACT_INSTRUMENTATION_BIN, // 1. + (coverageLevel == CoverageLevel::LineLevel ? "line" : "source"), // 2. + testTarget.m_testCoverageArtifact.c_str(), // 3. + LY_TEST_IMPACT_MODULES_DIR, // 4. + LY_TEST_IMPACT_AZ_TESTRUNNER_BIN, // 5. + sourcesFilter, // 6. + LY_TEST_IMPACT_AZ_TESTRUNNER_BIN, // 7. + testTarget.m_targetBinary.c_str(), // 8. + testTarget.m_testRunArtifact.c_str() // 9. + ); + + // OpenCppCoverage doesn't support forward slash directory separators so replace all with escaped backslashes + return AZStd::regex_replace(args, AZStd::regex("/"), "\\"); + } + + // Get the job command for an instrumented test run with valid source filters to produce coverage artifact + AZStd::string GetRunCommandForTargetWithSources(const TargetPaths& testTarget, CoverageLevel coverageLevel) + { + return GetRunCommandForTarget(testTarget, coverageLevel, LY_TEST_IMPACT_COVERAGE_SOURCES_DIR); + } + + // Get the job command for an instrumented test run without valid source filters to produce empty coverage artifact + AZStd::string GetRunCommandForTargetWithoutSources(const TargetPaths& testTarget, CoverageLevel coverageLevel) + { + return GetRunCommandForTarget(testTarget, coverageLevel, "C:\\No\\Sources\\Here\\At\\All\\Ever\\Ever\\Ever"); + } + + class InstrumentedTestRunnerFixture + : public AllocatorsTestFixture + { + public: + void SetUp() override; + void TearDown() override; + + protected: + using JobInfo = TestImpact::InstrumentedTestRunner::JobInfo; + using JobData = TestImpact::InstrumentedTestRunner::JobData; + + AZStd::vector m_jobInfos; + AZStd::unique_ptr m_testRunner; + AZStd::vector> m_testTargetJobArgs; + AZStd::vector m_testTargetPaths; + AZStd::vector m_expectedTestTargetRuns; + AZStd::vector> m_expectedTestTargetCoverages; + AZStd::vector m_expectedTestTargetResult; + size_t m_maxConcurrency = 0; + CoverageLevel m_coverageLevel = CoverageLevel::LineLevel; + inline static AZ::u32 s_uniqueTestCaseId = 0; // Unique id for each test case to be used as the suffix for the written files + }; + + void InstrumentedTestRunnerFixture::SetUp() + { + AllocatorsTestFixture::SetUp(); + + // Suffix each artifact file with the unique test case id to ensure that there are no possible race conditions between cleaning + // up the artifact files after each test case and those artifact files being written and read again in future test cases + const AZStd::string fileExtension = AZStd::string::format(".%u.xml", s_uniqueTestCaseId++); + + const AZStd::string runPath = AZStd::string(LY_TEST_IMPACT_TEST_TARGET_RESULTS_DIR) + "/%s.Run" + fileExtension; + const AZStd::string coveragePath = AZStd::string(LY_TEST_IMPACT_TEST_TARGET_COVERAGE_DIR) + "/%s.Coverage" + fileExtension; + + // TestTargetA + m_testTargetPaths.emplace_back(TargetPaths{ + LY_TEST_IMPACT_TEST_TARGET_A_BIN, AZStd::string::format(runPath.c_str(), LY_TEST_IMPACT_TEST_TARGET_A_BASE_NAME), + AZStd::string::format(coveragePath.c_str(), LY_TEST_IMPACT_TEST_TARGET_A_BASE_NAME)}); + + // TestTargetB + m_testTargetPaths.emplace_back(TargetPaths{ + LY_TEST_IMPACT_TEST_TARGET_B_BIN, AZStd::string::format(runPath.c_str(), LY_TEST_IMPACT_TEST_TARGET_B_BASE_NAME), + AZStd::string::format(coveragePath.c_str(), LY_TEST_IMPACT_TEST_TARGET_B_BASE_NAME)}); + + // TestTargetC + m_testTargetPaths.emplace_back(TargetPaths{ + LY_TEST_IMPACT_TEST_TARGET_C_BIN, AZStd::string::format(runPath.c_str(), LY_TEST_IMPACT_TEST_TARGET_C_BASE_NAME), + AZStd::string::format(coveragePath.c_str(), LY_TEST_IMPACT_TEST_TARGET_C_BASE_NAME)}); + + // TestTargetD + m_testTargetPaths.emplace_back(TargetPaths{ + LY_TEST_IMPACT_TEST_TARGET_D_BIN, AZStd::string::format(runPath.c_str(), LY_TEST_IMPACT_TEST_TARGET_D_BASE_NAME), + AZStd::string::format(coveragePath.c_str(), LY_TEST_IMPACT_TEST_TARGET_D_BASE_NAME)}); + + m_expectedTestTargetRuns.emplace_back(GetTestTargetATestRunSuites(), AZStd::chrono::milliseconds{500}); // TestTargetA + m_expectedTestTargetRuns.emplace_back(GetTestTargetBTestRunSuites(), AZStd::chrono::milliseconds{500}); // TestTargetB + m_expectedTestTargetRuns.emplace_back(GetTestTargetCTestRunSuites(), AZStd::chrono::milliseconds{500}); // TestTargetC + m_expectedTestTargetRuns.emplace_back(GetTestTargetDTestRunSuites(), AZStd::chrono::milliseconds{500}); // TestTargetD + + // TestTargetA + m_expectedTestTargetCoverages.emplace_back(AZStd::array{ + TestImpact::TestCoverage(GetTestTargetALineModuleCoverages()), + TestImpact::TestCoverage(GetTestTargetASourceModuleCoverages())}); + + // TestTargetB + m_expectedTestTargetCoverages.emplace_back(AZStd::array{ + TestImpact::TestCoverage(GetTestTargetBLineModuleCoverages()), + TestImpact::TestCoverage(GetTestTargetBSourceModuleCoverages())}); + + // TestTargetC + m_expectedTestTargetCoverages.emplace_back(AZStd::array{ + TestImpact::TestCoverage(GetTestTargetCLineModuleCoverages()), + TestImpact::TestCoverage(GetTestTargetCSourceModuleCoverages())}); + + // TestTargetD + m_expectedTestTargetCoverages.emplace_back(AZStd::array{ + TestImpact::TestCoverage(GetTestTargetDLineModuleCoverages()), + TestImpact::TestCoverage(GetTestTargetDSourceModuleCoverages())}); + + m_expectedTestTargetResult.emplace_back(TestImpact::TestRunResult::Failed); // TestTargetA + m_expectedTestTargetResult.emplace_back(TestImpact::TestRunResult::Passed); // TestTargetB + m_expectedTestTargetResult.emplace_back(TestImpact::TestRunResult::Passed); // TestTargetC + m_expectedTestTargetResult.emplace_back(TestImpact::TestRunResult::Passed); // TestTargetD + + // Generate the job command arguments for both line level and source level coverage permutations + for (const auto& testTarget : m_testTargetPaths) + { + m_testTargetJobArgs.emplace_back(AZStd::array{ + GetRunCommandForTargetWithSources(testTarget, CoverageLevel::LineLevel), + GetRunCommandForTargetWithSources(testTarget, CoverageLevel::SourceLevel)}); + } + } + + void InstrumentedTestRunnerFixture::TearDown() + { + DeleteFiles(LY_TEST_IMPACT_TEST_TARGET_COVERAGE_DIR, "*.xml"); + DeleteFiles(LY_TEST_IMPACT_TEST_TARGET_RESULTS_DIR, "*.xml"); + + AllocatorsTestFixture::TearDown(); + } + + using ConcurrencyAndCoveragePermutation = AZStd::tuple + < + size_t, // Max number of concurrent processes + CoverageLevel // Coverage level + >; + + // Fixture parameterized for different max number of concurrent jobs and coverage levels + class InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageParams + : public InstrumentedTestRunnerFixture + , public ::testing::WithParamInterface + { + public: + void SetUp() override + { + InstrumentedTestRunnerFixture::SetUp(); + auto [maxConcurrency, coverageLevel] = GetParam(); + m_maxConcurrency = maxConcurrency; + m_coverageLevel = coverageLevel; + } + }; + + using ConcurrencyAndJobExceptionPermutation = AZStd::tuple + < + size_t, // Max number of concurrent processes + CoverageLevel, // Coverage level + JobExceptionPolicy // Test job exception policy + >; + + // Fixture parameterized for different max number of concurrent jobs, coverage levels and different job exception policies + class InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageAndJobExceptionParams + : public InstrumentedTestRunnerFixture + , public ::testing::WithParamInterface + { + public: + void SetUp() override + { + InstrumentedTestRunnerFixture::SetUp(); + const auto& [maxConcurrency, coverageLevel, jobExceptionPolicy] = GetParam(); + m_maxConcurrency = maxConcurrency; + m_coverageLevel = coverageLevel; + m_jobExceptionPolicy = jobExceptionPolicy; + } + + protected: + JobExceptionPolicy m_jobExceptionPolicy = JobExceptionPolicy::Never; + }; + + class InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageAndFailedToLaunchExceptionParams + : public InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageAndJobExceptionParams + { + }; + + class InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageAndExecutedWithFailureExceptionParams + : public InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageAndJobExceptionParams + { + }; + + using ConcurrencyAndCoverageExceptionPermutation = AZStd::tuple + < + size_t, // Max number of concurrent processes + CoverageExceptionPolicy // Test coverage exception policy + >; + + // Fixture parameterized for different max number of concurrent jobs and different coverage exception policies + class InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageAndCoverageExceptionParams + : public InstrumentedTestRunnerFixture + , public ::testing::WithParamInterface + { + public: + void SetUp() override + { + InstrumentedTestRunnerFixture::SetUp(); + const auto& [maxConcurrency, coverageExceptionPolicy] = GetParam(); + m_maxConcurrency = maxConcurrency; + m_coverageExceptionPolicy = coverageExceptionPolicy; + } + + protected: + CoverageExceptionPolicy m_coverageExceptionPolicy = CoverageExceptionPolicy::Never; + }; + + namespace + { + AZStd::array MaxConcurrentRuns = {1, 2, 3, 4}; + + AZStd::array CoverageLevels = {CoverageLevel::LineLevel, CoverageLevel::SourceLevel}; + + AZStd::array FailedToLaunchExceptionPolicies = { + JobExceptionPolicy::Never, JobExceptionPolicy::OnFailedToExecute}; + + AZStd::array ExecutedWithFailureExceptionPolicies = { + JobExceptionPolicy::Never, JobExceptionPolicy::OnExecutedWithFailure}; + } // namespace + + // Validates that the specified test coverage matches the expected output + void ValidateTestTargetCoverage(const TestImpact::TestCoverage& actualResult, const TestImpact::TestCoverage& expectedResult) + { + EXPECT_TRUE(actualResult == expectedResult); + } + + // Validates that the specified test coverage is empty + void ValidateEmptyTestTargetCoverage(const TestImpact::TestCoverage& actualResult) + { + EXPECT_TRUE(actualResult.GetSourcesCovered().empty()); + EXPECT_TRUE(actualResult.GetModuleCoverages().empty()); + EXPECT_EQ(actualResult.GetNumSourcesCovered(), 0); + EXPECT_EQ(actualResult.GetNumModulesCovered(), 0); + } + + TEST_P( + InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageAndCoverageExceptionParams, + EmptyTestCoverages_ExpectEmptyTestCoveragesOrTestRunException) + { + // Given a test runner with no client callback or run timeout or runner timeout + m_testRunner = + AZStd::make_unique(AZStd::nullopt, m_maxConcurrency, AZStd::nullopt, AZStd::nullopt); + + // Given a mixture of instrumented test run jobs with and without coverage sources + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + const AZStd::string args = (jobId % 2) ? GetRunCommandForTargetWithoutSources(m_testTargetPaths[jobId], m_coverageLevel) + : m_testTargetJobArgs[jobId][m_coverageLevel]; + + JobData jobData(m_testTargetPaths[jobId].m_testRunArtifact, m_testTargetPaths[jobId].m_testCoverageArtifact); + m_jobInfos.emplace_back(JobInfo({jobId}, args, AZStd::move(jobData))); + } + + try + { + // When the instrumented test run jobs are executed with different exception policies + const auto runnerJobs = m_testRunner->RunInstrumentedTests(m_jobInfos, m_coverageExceptionPolicy, JobExceptionPolicy::Never); + + // Expect this statement to be reachable only if no exception policy for empty coverages + EXPECT_FALSE(::IsFlagSet(m_coverageExceptionPolicy, CoverageExceptionPolicy::OnEmptyCoverage)); + + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value().first, m_expectedTestTargetRuns[jobId]); + + if (jobId % 2) + { + // Expect jobs have a empty test coverages + ValidateEmptyTestTargetCoverage(job.GetPayload().value().second); + } + else + { + // Expect the jobs to successfully result in a test run and coverage that matches the expected test run data + ValidateTestTargetCoverage(job.GetPayload().value().second, m_expectedTestTargetCoverages[jobId][m_coverageLevel]); + } + } + } + catch ([[maybe_unused]] const TestImpact::TestRunException& e) + { + // Expect this statement to be reachable only if there is an exception policy for empty coverages + EXPECT_TRUE(::IsFlagSet(m_coverageExceptionPolicy, CoverageExceptionPolicy::OnEmptyCoverage)); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_P( + InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageAndFailedToLaunchExceptionParams, + InvalidCommandArgument_ExpectJobResulFailedToExecuteeOrTestJobException) + { + // Given a test runner with no client callback or run timeout or runner timeout + m_testRunner = + AZStd::make_unique(AZStd::nullopt, m_maxConcurrency, AZStd::nullopt, AZStd::nullopt); + + // Given a mixture of instrumented test run jobs with valid and invalid command arguments + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + const AZStd::string args = (jobId % 2) ? InvalidProcessPath : m_testTargetJobArgs[jobId][m_coverageLevel]; + JobData jobData(m_testTargetPaths[jobId].m_testRunArtifact, m_testTargetPaths[jobId].m_testCoverageArtifact); + m_jobInfos.emplace_back(JobInfo({jobId}, args, AZStd::move(jobData))); + } + + try + { + // When the instrumented test run jobs are executed with different exception policies + const auto runnerJobs = m_testRunner->RunInstrumentedTests(m_jobInfos, CoverageExceptionPolicy::Never, m_jobExceptionPolicy); + + // Expect this statement to be reachable only if no exception policy for launch failures + EXPECT_FALSE(::IsFlagSet(m_jobExceptionPolicy, JobExceptionPolicy::OnFailedToExecute)); + + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + if (jobId % 2) + { + // Expect invalid jobs have a job result of FailedToExecute + ValidateJobFailedToExecute(job); + } + else + { + // Expect the valid jobs to successfully result in a test run that matches the expected test run data + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value().first, m_expectedTestTargetRuns[jobId]); + ValidateTestTargetCoverage(job.GetPayload().value().second, m_expectedTestTargetCoverages[jobId][m_coverageLevel]); + } + } + } + catch ([[maybe_unused]] const TestImpact::TestJobException& e) + { + // Expect this statement to be reachable only if there is an exception policy for launch failures + EXPECT_TRUE(::IsFlagSet(m_jobExceptionPolicy, JobExceptionPolicy::OnFailedToExecute)); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_P( + InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageAndExecutedWithFailureExceptionParams, + ErroneousReturnCode_ExpectJobResultExecutedWithFailureOrTestJobException) + { + // Given a test runner with no client callback or run timeout or runner timeout + m_testRunner = + AZStd::make_unique(AZStd::nullopt, m_maxConcurrency, AZStd::nullopt, AZStd::nullopt); + + // Given a mixture of instrumented test run jobs that execute and return either successfully or with failure + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].m_testRunArtifact, m_testTargetPaths[jobId].m_testCoverageArtifact); + m_jobInfos.emplace_back(JobInfo({jobId}, m_testTargetJobArgs[jobId][m_coverageLevel], AZStd::move(jobData))); + } + + try + { + // When the instrumented test run jobs are executed with different exception policies + const auto runnerJobs = m_testRunner->RunInstrumentedTests(m_jobInfos, CoverageExceptionPolicy::Never, m_jobExceptionPolicy); + + // Expect this statement to be reachable only if no exception policy for jobs that return with error + EXPECT_FALSE(::IsFlagSet(m_jobExceptionPolicy, JobExceptionPolicy::OnExecutedWithFailure)); + + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + + // Expect the valid jobs to successfully result in a test run that matches the expected test run data + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value().first, m_expectedTestTargetRuns[jobId]); + ValidateTestTargetCoverage(job.GetPayload().value().second, m_expectedTestTargetCoverages[jobId][m_coverageLevel]); + } + } + catch ([[maybe_unused]] const TestImpact::TestJobException& e) + { + // Expect this statement to be reachable only if there is an exception policy for jobs that return with error + EXPECT_TRUE(::IsFlagSet(m_jobExceptionPolicy, JobExceptionPolicy::OnExecutedWithFailure)); + } + catch ([[maybe_unused]] const TestImpact::Exception& e) + { + FAIL(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(InstrumentedTestRunnerFixture, EmptyRunRawData_ExpectTestRunnerException) + { + // Given a test runner with no client callback, concurrency, run timeout or runner timeout + m_testRunner = + AZStd::make_unique(AZStd::nullopt, OneConcurrentProcess, AZStd::nullopt, AZStd::nullopt); + + // Given an test runner job that will return successfully but with an empty artifact string + JobData jobData("", m_testTargetPaths[TestTargetA].m_testCoverageArtifact); + m_jobInfos.emplace_back(JobInfo({TestTargetA}, m_testTargetJobArgs[TestTargetA][LineLevel], AZStd::move(jobData))); + + try + { + // When the test runner job is executed + const auto runnerJobs = + m_testRunner->RunInstrumentedTests(m_jobInfos, CoverageExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::TestRunException& e) + { + // Expect an runner exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(InstrumentedTestRunnerFixture, EmptyCoverageRawData_ExpectTestRunnerException) + { + // Given a test runner with no client callback, concurrency, run timeout or runner timeout + m_testRunner = + AZStd::make_unique(AZStd::nullopt, OneConcurrentProcess, AZStd::nullopt, AZStd::nullopt); + + // Given an test runner job that will return successfully but with an empty artifact string + JobData jobData(m_testTargetPaths[TestTargetA].m_testRunArtifact, ""); + m_jobInfos.emplace_back(JobInfo({TestTargetA}, m_testTargetJobArgs[TestTargetA][LineLevel], AZStd::move(jobData))); + + try + { + // When the test runner job is executed + const auto runnerJobs = + m_testRunner->RunInstrumentedTests(m_jobInfos, CoverageExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::TestRunException& e) + { + // Expect an runner exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(InstrumentedTestRunnerFixture, InvalidRunArtifact_ExpectArtifactException) + { + // Given a run artifact with invalid contents + WriteTextToFile("There is nothing valid here", m_testTargetPaths[TestTargetA].m_testRunArtifact); + + // Given a job command that will write the run artifact to a different location that what we will read from + TargetPaths invalidRunArtifact = m_testTargetPaths[TestTargetA]; + invalidRunArtifact.m_testRunArtifact /= ".xml"; + const AZStd::string args = GetRunCommandForTargetWithSources(invalidRunArtifact, LineLevel); + + // Given a test runner with no client callback, concurrency, run timeout or runner timeout + m_testRunner = + AZStd::make_unique(AZStd::nullopt, OneConcurrentProcess, AZStd::nullopt, AZStd::nullopt); + + // Given an test runner job that will return successfully but not produce a run artifact + JobData jobData(m_testTargetPaths[TestTargetA].m_testRunArtifact, m_testTargetPaths[TestTargetA].m_testCoverageArtifact); + m_jobInfos.emplace_back(JobInfo({TestTargetA}, args, AZStd::move(jobData))); + + try + { + // When the test runner job is executed + const auto runnerJobs = + m_testRunner->RunInstrumentedTests(m_jobInfos, CoverageExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an runner exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(InstrumentedTestRunnerFixture, InvalidCoverageArtifact_ExpectArtifactException) + { + // Given a coverage artifact with invalid contents + WriteTextToFile("There is nothing valid here", m_testTargetPaths[TestTargetA].m_testCoverageArtifact); + + // Given a job command that will write the coverage artifact to a different location that what we will read from + TargetPaths invalidCoverageArtifact = m_testTargetPaths[TestTargetA]; + invalidCoverageArtifact.m_testCoverageArtifact /= ".xml"; + const AZStd::string args = GetRunCommandForTargetWithSources(invalidCoverageArtifact, LineLevel); + + // Given a test runner with no client callback, concurrency, run timeout or runner timeout + m_testRunner = + AZStd::make_unique(AZStd::nullopt, OneConcurrentProcess, AZStd::nullopt, AZStd::nullopt); + + // Given an test runner job that will return successfully but not produce a run artifact + JobData jobData(m_testTargetPaths[TestTargetA].m_testRunArtifact, m_testTargetPaths[TestTargetA].m_testCoverageArtifact); + m_jobInfos.emplace_back(JobInfo({TestTargetA}, args, AZStd::move(jobData))); + + try + { + // When the test runner job is executed + const auto runnerJobs = + m_testRunner->RunInstrumentedTests(m_jobInfos, CoverageExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an runner exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_P(InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageParams, RunTestTargets_RunsAndCoverageMatchTestSuitesInTarget) + { + // Given a test runner with no client callback, runner timeout or runner timeout + m_testRunner = + AZStd::make_unique(AZStd::nullopt, m_maxConcurrency, AZStd::nullopt, AZStd::nullopt); + + // Given an test runner job for each test target with no runner caching + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].m_testRunArtifact, m_testTargetPaths[jobId].m_testCoverageArtifact); + m_jobInfos.emplace_back(JobInfo({jobId}, m_testTargetJobArgs[jobId][m_coverageLevel], AZStd::move(jobData))); + } + + // When the test runner jobs are executed + const auto runnerJobs = m_testRunner->RunInstrumentedTests(m_jobInfos, CoverageExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Expect each job to successfully result in a test runner that matches the expected test runner data for that test target + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value().first, m_expectedTestTargetRuns[jobId]); + ValidateTestTargetCoverage(job.GetPayload().value().second, m_expectedTestTargetCoverages[jobId][m_coverageLevel]); + } + } + + TEST_P( + InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageParams, + RunTestTargetsWithArbitraryJobIds_RunsAndCoverageMatchTestSuitesInTarget) + { + // Given a set of arbitrary job ids to be used for the test target jobs + enum + { + ArbitraryA = 36, + ArbitraryB = 890, + ArbitraryC = 19, + ArbitraryD = 1 + }; + + const AZStd::unordered_map sequentialToArbitrary = + { + {TestTargetA, ArbitraryA}, + {TestTargetB, ArbitraryB}, + {TestTargetC, ArbitraryC}, + {TestTargetD, ArbitraryD}, + }; + + const AZStd::unordered_map arbitraryToSequential = + { + {ArbitraryA, TestTargetA}, + {ArbitraryB, TestTargetB}, + {ArbitraryC, TestTargetC}, + {ArbitraryD, TestTargetD}, + }; + + // Given a test runner with no client callback, run timeout or runner timeout + m_testRunner = + AZStd::make_unique(AZStd::nullopt, m_maxConcurrency, AZStd::nullopt, AZStd::nullopt); + + // Given an test run job for each test target + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].m_testRunArtifact, m_testTargetPaths[jobId].m_testCoverageArtifact); + m_jobInfos.emplace_back( + JobInfo({sequentialToArbitrary.at(jobId)}, m_testTargetJobArgs[jobId][m_coverageLevel], AZStd::move(jobData))); + } + + // When the instrumented test run jobs are executed + const auto runnerJobs = m_testRunner->RunInstrumentedTests(m_jobInfos, CoverageExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Expect each job to successfully result in a test run that matches the expected test run data for that test target + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = arbitraryToSequential.at(job.GetJobInfo().GetId().m_value); + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value().first, m_expectedTestTargetRuns[jobId]); + ValidateTestTargetCoverage(job.GetPayload().value().second, m_expectedTestTargetCoverages[jobId][m_coverageLevel]); + } + } + + TEST_P(InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageParams, RunTestTargetsWithCallback_RunsAndCoverageMatchTestSuitesInTarget) + { + // Given a client callback function that tracks the number of successful runs + size_t numSuccesses = 0; + const auto jobCallback = + [&numSuccesses]([[maybe_unused]] const TestImpact::InstrumentedTestRunner::JobInfo& jobInfo, const TestImpact::JobMeta& meta) + { + if (meta.m_result == TestImpact::JobResult::ExecutedWithSuccess) + { + numSuccesses++; + } + }; + + // Given a test runner with no run timeout or runner timeout + m_testRunner = + AZStd::make_unique(jobCallback, m_maxConcurrency, AZStd::nullopt, AZStd::nullopt); + + // Given an test run job for each test target + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].m_testRunArtifact, m_testTargetPaths[jobId].m_testCoverageArtifact); + m_jobInfos.emplace_back(JobInfo({jobId}, m_testTargetJobArgs[jobId][m_coverageLevel], AZStd::move(jobData))); + } + + // When the instrumented test run jobs are executed + const auto runnerJobs = m_testRunner->RunInstrumentedTests(m_jobInfos, CoverageExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Expect the number of successful runs tracked in the callback to match the number of test targets run with no failures + EXPECT_EQ( + numSuccesses, + AZStd::count_if(m_expectedTestTargetResult.begin(), m_expectedTestTargetResult.end(), [](TestImpact::TestRunResult result) { + return result == TestImpact::TestRunResult::Passed; + })); + + // Expect each job to successfully result in a test run that matches the expected test run data for that test target + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value().first, m_expectedTestTargetRuns[jobId]); + ValidateTestTargetCoverage(job.GetPayload().value().second, m_expectedTestTargetCoverages[jobId][m_coverageLevel]); + } + } + + TEST_P(InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageParams, JobRunnerTimeout_InFlightJobsTimeoutAndQueuedJobsUnlaunched) + { + // Given a test runner with no client callback or runner timeout and 2 second run timeout + m_testRunner = AZStd::make_unique( + AZStd::nullopt, m_maxConcurrency, AZStd::chrono::seconds(2), AZStd::nullopt); + + // Given an test run job for each test target where half will sleep indefinitely + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].m_testRunArtifact, m_testTargetPaths[jobId].m_testCoverageArtifact); + const AZStd::string args = (jobId % 2) + ? AZStd::string::format("%s %s", ValidProcessPath, ConstructTestProcessArgs(jobId, LongSleep).c_str()) + : m_testTargetJobArgs[jobId][m_coverageLevel]; + m_jobInfos.emplace_back(JobInfo({jobId}, args, AZStd::move(jobData))); + } + + // When the instrumented test run jobs are executed + const auto runnerJobs = m_testRunner->RunInstrumentedTests(m_jobInfos, CoverageExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Expect half the jobs to successfully result in a test run that matches the expected test run data for that test target + // with the other half having timed out + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + if (jobId % 2) + { + ValidateJobTimeout(job); + } + else + { + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value().first, m_expectedTestTargetRuns[jobId]); + ValidateTestTargetCoverage(job.GetPayload().value().second, m_expectedTestTargetCoverages[jobId][m_coverageLevel]); + } + } + } + + TEST_F(InstrumentedTestRunnerFixture, JobTimeout_InFlightJobTimeoutAndQueuedJobsUnlaunched) + { + // Given a test runner with no client callback or run timeout and a 5 second runner timeout + m_testRunner = AZStd::make_unique( + AZStd::nullopt, FourConcurrentProcesses, AZStd::nullopt, AZStd::chrono::seconds(5)); + + // Given an test run job for each test target where half will sleep indefinitely + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].m_testRunArtifact, m_testTargetPaths[jobId].m_testCoverageArtifact); + const AZStd::string args = (jobId % 2) + ? AZStd::string::format("%s %s", ValidProcessPath, ConstructTestProcessArgs(jobId, LongSleep).c_str()) + : m_testTargetJobArgs[jobId][m_coverageLevel]; + m_jobInfos.emplace_back(JobInfo({jobId}, args, AZStd::move(jobData))); + } + + // When the instrumented test run jobs are executed + const auto runnerJobs = m_testRunner->RunInstrumentedTests(m_jobInfos, CoverageExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Expect half the jobs to successfully result in a test run that matches the expected test run data for that test target + // with the other half having timed out + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + if (jobId % 2) + { + ValidateJobTimeout(job); + } + else + { + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value().first, m_expectedTestTargetRuns[jobId]); + ValidateTestTargetCoverage(job.GetPayload().value().second, m_expectedTestTargetCoverages[jobId][m_coverageLevel]); + } + } + } + + INSTANTIATE_TEST_CASE_P( + , + InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageAndCoverageExceptionParams, + ::testing::Combine( + ::testing::ValuesIn(MaxConcurrentRuns), + ::testing::Values(CoverageExceptionPolicy::Never, CoverageExceptionPolicy::OnEmptyCoverage))); + + INSTANTIATE_TEST_CASE_P( + , + InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageAndFailedToLaunchExceptionParams, + ::testing::Combine( + ::testing::ValuesIn(MaxConcurrentRuns), ::testing::ValuesIn(CoverageLevels), + ::testing::ValuesIn(FailedToLaunchExceptionPolicies))); + + INSTANTIATE_TEST_CASE_P( + , + InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageAndExecutedWithFailureExceptionParams, + ::testing::Combine( + ::testing::ValuesIn(MaxConcurrentRuns), ::testing::ValuesIn(CoverageLevels), + ::testing::ValuesIn(ExecutedWithFailureExceptionPolicies))); + + INSTANTIATE_TEST_CASE_P( + , + InstrumentedTestRunnerFixtureWithConcurrencyAndCoverageParams, + ::testing::Combine(::testing::ValuesIn(MaxConcurrentRuns), ::testing::ValuesIn(CoverageLevels))); +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestCoverageTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestCoverageTest.cpp new file mode 100644 index 0000000000..538097d11e --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestCoverageTest.cpp @@ -0,0 +1,208 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include + +namespace UnitTest +{ + namespace + { + AZ::IO::Path GenerateSourcePath(AZ::u32 index) + { + return AZStd::string::format("SourceFile%u", index); + } + + AZ::IO::Path GenerateModulePath(AZ::u32 index) + { + return AZStd::string::format("Module%u", index); + } + + AZStd::vector GenerateLineCoverages(AZ::u32 numLines) + { + AZStd::vector lineCoverages; + for (size_t i = 0; i < numLines; i++) + { + // Fudge some superficially different but trivially checkable line coverage data + lineCoverages.emplace_back(TestImpact::LineCoverage{i, i * 2}); + } + + return lineCoverages; + } + + TestImpact::SourceCoverage GenerateSourceCoverage(AZ::u32 index, TestImpact::CoverageLevel coverageLevel) + { + TestImpact::SourceCoverage sourceCoverage; + sourceCoverage.m_path = GenerateSourcePath(index); + if (coverageLevel == TestImpact::CoverageLevel::Line) + { + sourceCoverage.m_coverage.emplace(GenerateLineCoverages(index + 1)); + } + + return sourceCoverage; + } + + AZStd::vector GenerateSourceCoverages(AZ::u32 numSources, TestImpact::CoverageLevel coverageLevel) + { + AZStd::vector sourceCoverages; + for (AZ::u32 i = 0; i < numSources; i++) + { + sourceCoverages.emplace_back(GenerateSourceCoverage(i, coverageLevel)); + } + + return sourceCoverages; + } + + TestImpact::ModuleCoverage GenerateModuleCoverage(AZ::u32 index, AZ::u32 numSources, TestImpact::CoverageLevel coverageLevel) + { + TestImpact::ModuleCoverage moduleCoverage; + moduleCoverage.m_path = GenerateModulePath(index); + moduleCoverage.m_sources = GenerateSourceCoverages(numSources, coverageLevel); + return moduleCoverage; + } + + AZStd::vector GenerateModuleCoverages(AZ::u32 numModules, TestImpact::CoverageLevel coverageLevel) + { + AZStd::vector moduleCoverages; + for (AZ::u32 i = 0; i < numModules; i++) + { + // Fudge some superficially different but trivially deducible module coverage data + moduleCoverages.emplace_back(GenerateModuleCoverage(i, i + 1, coverageLevel)); + } + + return moduleCoverages; + } + } // namespace + + using CoveragePermutation = AZStd::tuple + < + unsigned, // Number of modules covered + TestImpact::CoverageLevel // Test coverage level + >; + + // Fixture parameterized for different max number of concurrent jobs + class TestCoverageFixtureWithCoverageParams + : public AllocatorsTestFixture + , public ::testing::WithParamInterface + { + public: + void SetUp() override; + + protected: + void ValidateTestCoverage(const TestImpact::TestCoverage& testCoverage); + + size_t m_numModulesCovered; + TestImpact::CoverageLevel m_coverageLevel; + }; + + void TestCoverageFixtureWithCoverageParams::SetUp() + { + AllocatorsTestFixture::SetUp(); + const auto& [numModulesCovered, coverageLevel] = GetParam(); + m_numModulesCovered = numModulesCovered; + m_coverageLevel = coverageLevel; + } + + void TestCoverageFixtureWithCoverageParams::ValidateTestCoverage(const TestImpact::TestCoverage& testCoverage) + { + // Expect the coverage level to match that which was used to generate the module coverages generated + EXPECT_EQ(testCoverage.GetCoverageLevel(), m_coverageLevel); + + // Expect the number of modules covered to match the number of module coverages generated + EXPECT_EQ(testCoverage.GetNumModulesCovered(), m_numModulesCovered); + + // Expect the number of unique sources covered to match the number of modules coverages generated + EXPECT_EQ(testCoverage.GetNumSourcesCovered(), m_numModulesCovered); + + // Expect the unique sources covered to match the procedurally generated source paths + for (size_t sourceIndex = 0; sourceIndex < testCoverage.GetNumSourcesCovered(); sourceIndex++) + { + EXPECT_EQ(testCoverage.GetSourcesCovered()[sourceIndex], GenerateSourcePath(sourceIndex)); + } + + // Expect each module covered to match that of the corresponding procedurally generated modules + for (size_t moduleIndex = 0; moduleIndex < testCoverage.GetNumModulesCovered(); moduleIndex++) + { + const TestImpact::ModuleCoverage& moduleCoverage = testCoverage.GetModuleCoverages()[moduleIndex]; + + // Expect the module path to match that of the corresponding procedurally generated module + EXPECT_EQ(moduleCoverage.m_path, GenerateModulePath(moduleIndex)); + + // Expect the module's number of sources to match that of the corresponding procedurally generated module + EXPECT_EQ(moduleCoverage.m_sources.size(), moduleIndex + 1); + + for (size_t sourceIndex = 0; sourceIndex < moduleCoverage.m_sources.size(); sourceIndex++) + { + const TestImpact::SourceCoverage& sourceCoverage = moduleCoverage.m_sources[sourceIndex]; + + // Expect the source path to match the procedurally generated source path + EXPECT_EQ(sourceCoverage.m_path, GenerateSourcePath(sourceIndex)); + + if (m_coverageLevel == TestImpact::CoverageLevel::Line) + { + // Expect there to actually be line coverage data if this coverage was procedurally generated with line data + EXPECT_TRUE(sourceCoverage.m_coverage.has_value()); + + const AZStd::vector& lineCoverages = sourceCoverage.m_coverage.value(); + + // Expect the source's number of lines to match that of the corresponding procedurally generated source + EXPECT_EQ(lineCoverages.size(), sourceIndex + 1); + + for (size_t lineIndex = 0; lineIndex < lineCoverages.size(); lineIndex++) + { + // The expected line number and hit count are deduced as follows: + // Line number: line index + // Hit count: 2x line index + EXPECT_EQ(lineCoverages[lineIndex].m_lineNumber, lineIndex); + EXPECT_EQ(lineCoverages[lineIndex].m_hitCount, lineIndex * 2); + } + } + else + { + // Do not expect there to actually be line coverage data if this coverage was not procedurally generated with line data + EXPECT_FALSE(sourceCoverage.m_coverage.has_value()); + } + } + } + } + + TEST(TestCoverage, EmptyCoverage_ExpectTestRunException) + { + // When constructing a test coverage from the empty module coverages + TestImpact::TestCoverage testCoverage(AZStd::vector{}); + + // Expect the test coverage fields to be empty + EXPECT_EQ(testCoverage.GetNumModulesCovered(), 0); + EXPECT_EQ(testCoverage.GetNumSourcesCovered(), 0); + EXPECT_TRUE(testCoverage.GetModuleCoverages().empty()); + EXPECT_TRUE(testCoverage.GetSourcesCovered().empty()); + } + + TEST_P(TestCoverageFixtureWithCoverageParams, AllCoveragePermutations_ExpectTestCoverageMetaDatasToMatchPermutations) + { + // Given a procedurally generated test coverage + const TestImpact::TestCoverage testCoverage(GenerateModuleCoverages(m_numModulesCovered, m_coverageLevel)); + + // Expect the test coverage data and meta-data to match that of the rules used to procedurally generate the coverage data + ValidateTestCoverage(testCoverage); + } + + INSTANTIATE_TEST_CASE_P( + , + TestCoverageFixtureWithCoverageParams, + ::testing::Combine( + ::testing::Range(1u, 11u), + ::testing::Values(TestImpact::CoverageLevel::Line, TestImpact::CoverageLevel::Source)) + ); +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestEnumeratorTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestEnumeratorTest.cpp new file mode 100644 index 0000000000..9b67eafa54 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestEnumeratorTest.cpp @@ -0,0 +1,890 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + using JobExceptionPolicy = TestImpact::TestEnumerator::JobExceptionPolicy; + using CacheExceptionPolicy = TestImpact::TestEnumerator::CacheExceptionPolicy; + + // Generates the command to run the given test target through AzTestRunner and get gtest to output the enumeration file + AZStd::string GetEnumerateCommandForTarget(AZStd::pair testTarget) + { + return AZStd::string::format( + "%s %s AzRunUnitTests --gtest_list_tests --gtest_output=xml:%s", + LY_TEST_IMPACT_AZ_TESTRUNNER_BIN, + testTarget.first.c_str(), // Path to test target bin + testTarget.second.c_str()); // Path to test target gtest enumeration file + } + + class TestEnumeratorFixture + : public AllocatorsTestFixture + { + public: + void SetUp() override; + + protected: + using JobInfo = TestImpact::TestEnumerator::JobInfo; + using JobData = TestImpact::TestEnumerator::JobData; + + AZStd::vector m_jobInfos; + AZStd::unique_ptr m_testEnumerator; + AZStd::vector m_testTargetJobArgs; + AZStd::vector> m_testTargetPaths; + AZStd::vector m_expectedTestTargetEnumerations; + AZStd::vector m_cacheFiles; + size_t m_maxConcurrency = 0; + }; + + void TestEnumeratorFixture::SetUp() + { + UnitTest::AllocatorsTestFixture::SetUp(); + + DeleteFiles(LY_TEST_IMPACT_TEST_TARGET_ENUMERATION_DIR, "*.cache"); + DeleteFiles(LY_TEST_IMPACT_TEST_TARGET_ENUMERATION_DIR, "*.xml"); + + // first: path to test target bin + // second: path to test target gtest enumeration file in XML format + const AZStd::string enumPath = AZStd::string(LY_TEST_IMPACT_TEST_TARGET_ENUMERATION_DIR) + "/%s.Enumeration.xml"; + m_testTargetPaths.emplace_back( + LY_TEST_IMPACT_TEST_TARGET_A_BIN, AZStd::string::format(enumPath.c_str(), LY_TEST_IMPACT_TEST_TARGET_A_BASE_NAME)); + m_testTargetPaths.emplace_back( + LY_TEST_IMPACT_TEST_TARGET_B_BIN, AZStd::string::format(enumPath.c_str(), LY_TEST_IMPACT_TEST_TARGET_B_BASE_NAME)); + m_testTargetPaths.emplace_back( + LY_TEST_IMPACT_TEST_TARGET_C_BIN, AZStd::string::format(enumPath.c_str(), LY_TEST_IMPACT_TEST_TARGET_C_BASE_NAME)); + m_testTargetPaths.emplace_back( + LY_TEST_IMPACT_TEST_TARGET_D_BIN, AZStd::string::format(enumPath.c_str(), LY_TEST_IMPACT_TEST_TARGET_D_BASE_NAME)); + + m_expectedTestTargetEnumerations.emplace_back(GetTestTargetATestEnumerationSuites()); + m_expectedTestTargetEnumerations.emplace_back(GetTestTargetBTestEnumerationSuites()); + m_expectedTestTargetEnumerations.emplace_back(GetTestTargetCTestEnumerationSuites()); + m_expectedTestTargetEnumerations.emplace_back(GetTestTargetDTestEnumerationSuites()); + + // Path to enumeration file in TIAF internal JSON format + m_cacheFiles.emplace_back( + AZStd::string::format("%s/%s.cache", LY_TEST_IMPACT_TEST_TARGET_ENUMERATION_DIR, LY_TEST_IMPACT_TEST_TARGET_A_BASE_NAME)); + m_cacheFiles.emplace_back( + AZStd::string::format("%s/%s.cache", LY_TEST_IMPACT_TEST_TARGET_ENUMERATION_DIR, LY_TEST_IMPACT_TEST_TARGET_B_BASE_NAME)); + m_cacheFiles.emplace_back( + AZStd::string::format("%s/%s.cache", LY_TEST_IMPACT_TEST_TARGET_ENUMERATION_DIR, LY_TEST_IMPACT_TEST_TARGET_C_BASE_NAME)); + m_cacheFiles.emplace_back( + AZStd::string::format("%s/%s.cache", LY_TEST_IMPACT_TEST_TARGET_ENUMERATION_DIR, LY_TEST_IMPACT_TEST_TARGET_D_BASE_NAME)); + + for (const auto& testTarget : m_testTargetPaths) + { + m_testTargetJobArgs.emplace_back(GetEnumerateCommandForTarget(testTarget)); + } + } + + // Fixture parameterized for different max number of concurrent jobs + class TestEnumeratorFixtureWithConcurrencyParams + : public TestEnumeratorFixture + , public ::testing::WithParamInterface + { + public: + void SetUp() override + { + TestEnumeratorFixture::SetUp(); + m_maxConcurrency = GetParam(); + } + }; + + using ConcurrencyAndJobExceptionPermutation = AZStd::tuple + < + size_t, // Max number of concurrent processes + JobExceptionPolicy // Test job exception policy + >; + + // Fixture parameterized for different max number of concurrent jobs and different job exception policies + class TestEnumeratorFixtureWithConcurrencyAndJobExceptionParams + : public TestEnumeratorFixture + , public ::testing::WithParamInterface + { + public: + void SetUp() override + { + TestEnumeratorFixture::SetUp(); + const auto& [maxConcurrency, jobExceptionPolicy] = GetParam(); + m_maxConcurrency = maxConcurrency; + m_jobExceptionPolicy = jobExceptionPolicy; + } + + protected: + JobExceptionPolicy m_jobExceptionPolicy = JobExceptionPolicy::Never; + }; + + using ConcurrencyAndCacheExceptionPermutation = AZStd::tuple + < + size_t, // Max number of concurrent processes + CacheExceptionPolicy // Test enumeration exception policy + >; + + // Fixture parameterized for different max number of concurrent jobs and different enumeration exception policies + class TestEnumeratorFixtureWithConcurrencyAndCacheExceptionParams + : public TestEnumeratorFixture + , public ::testing::WithParamInterface + { + public: + void SetUp() override + { + TestEnumeratorFixture::SetUp(); + const auto& [maxConcurrency, cacheExceptionPolicy] = GetParam(); + m_maxConcurrency = maxConcurrency; + m_cacheExceptionPolicy = cacheExceptionPolicy; + } + + protected: + CacheExceptionPolicy m_cacheExceptionPolicy = CacheExceptionPolicy::Never; + }; + + namespace + { + AZStd::array MaxConcurrentEnumerations = {{1, 2, 3, 4}}; + + AZStd::array JobExceptionPolicies = + { + JobExceptionPolicy::Never, + JobExceptionPolicy::OnExecutedWithFailure, + JobExceptionPolicy::OnFailedToExecute + }; + + AZStd::array CacheExceptionPolicies = + { + CacheExceptionPolicy::Never, + CacheExceptionPolicy::OnCacheNotExist, + CacheExceptionPolicy::OnCacheReadFailure, + CacheExceptionPolicy::OnCacheWriteFailure + }; + } + + // Validates that the specified job successfully read from its test enumeration cache + void ValidateJobSuccessfulCacheRead(const TestImpact::TestEnumerator::Job& job) + { + EXPECT_EQ(job.GetResult(), TestImpact::JobResult::NotExecuted); + EXPECT_EQ(job.GetStartTime(), AZStd::chrono::high_resolution_clock::time_point()); + EXPECT_EQ(job.GetEndTime(), AZStd::chrono::high_resolution_clock::time_point()); + EXPECT_EQ(job.GetDuration(), AZStd::chrono::milliseconds(0)); + EXPECT_FALSE(job.GetReturnCode().has_value()); + EXPECT_TRUE(job.GetPayload().has_value()); + } + + // Validates that the specified test enumeration matched the expected output + void ValidateTestTargetEnumeration(const TestImpact::TestEnumeration& actualResult, const TestImpact::TestEnumeration& expectedResult) + { + EXPECT_TRUE(actualResult == expectedResult); + EXPECT_EQ(actualResult.GetNumTestSuites(), CalculateNumTestSuites(expectedResult.GetTestSuites())); + EXPECT_EQ(actualResult.GetNumTests(), CalculateNumTests(expectedResult.GetTestSuites())); + EXPECT_EQ(actualResult.GetNumEnabledTests(), CalculateNumEnabledTests(expectedResult.GetTestSuites())); + EXPECT_EQ(actualResult.GetNumDisabledTests(), CalculateNumDisabledTests(expectedResult.GetTestSuites())); + } + + // Validates that the specified test enumeration cache matches the expected output + void ValidateTestEnumerationCache(const AZ::IO::Path& cacheFile, const TestImpact::TestEnumeration& expectedEnumeration) + { + // Cache file must exist + const auto fileSize = AZ::IO::SystemFile::Length(cacheFile.c_str()); + EXPECT_GT(fileSize, 0); + + // Read raw byte data from cache + AZStd::vector buffer(fileSize + 1); + buffer[fileSize] = 0; + EXPECT_TRUE(AZ::IO::SystemFile::Read(cacheFile.c_str(), buffer.data())); + + // Transform raw byte data to raw string data and attempt to construct the test enumeration + AZStd::string rawEnum(buffer.begin(), buffer.end()); + TestImpact::TestEnumeration actualEnumeration = TestImpact::DeserializeTestEnumeration(rawEnum); + + // Check that the constructed test enumeration matches the expected enumeration + ValidateTestTargetEnumeration(actualEnumeration, expectedEnumeration); + } + + // Validates that the specified cache file does not exist + void ValidateInvalidTestEnumerationCache(const AZ::IO::Path& cacheFile) + { + EXPECT_FALSE(AZ::IO::SystemFile::Exists(cacheFile.c_str())); + } + + TEST_P( + TestEnumeratorFixtureWithConcurrencyAndJobExceptionParams, InvalidCommandArgument_ExpectJobResulFailedToExecuteeOrTestJobException) + { + // Given a test enumerator with no client callback or enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + m_maxConcurrency, + AZStd::nullopt, + AZStd::nullopt); + + // Given a mixture of test enumeration jobs with valid and invalid command arguments + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + const AZStd::string args = (jobId % 2) ? InvalidProcessPath : m_testTargetJobArgs[jobId]; + JobData jobData(m_testTargetPaths[jobId].second, AZStd::nullopt); + m_jobInfos.emplace_back(JobInfo({jobId}, args, AZStd::move(jobData))); + } + + try + { + // When the test enumeration jobs are executed with different exception policies + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, CacheExceptionPolicy::Never, m_jobExceptionPolicy); + + // Expect this statement to be reachable only if no exception policy for launch failures + EXPECT_FALSE(::IsFlagSet(m_jobExceptionPolicy, JobExceptionPolicy::OnFailedToExecute)); + + for (const auto& job : enumerationJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + if (jobId % 2) + { + // Expect invalid jobs have a job result of FailedToExecute + ValidateJobFailedToExecute(job); + } + else + { + // Expect the valid jobs job to successfully result in a test enumeration that matches the expected test enumeration data + ValidateJobExecutedSuccessfully(job); + ValidateTestTargetEnumeration(job.GetPayload().value(), m_expectedTestTargetEnumerations[jobId]); + } + } + } + catch ([[maybe_unused]] const TestImpact::TestJobException& e) + { + // Expect this statement to be reachable only if there is an exception policy for launch failures + EXPECT_TRUE(::IsFlagSet(m_jobExceptionPolicy, JobExceptionPolicy::OnFailedToExecute)); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_P( + TestEnumeratorFixtureWithConcurrencyAndJobExceptionParams, ErroneousReturnCode_ExpectJobResultExecutedWithFailureOrTestJobException) + { + // Given a test enumerator with no client callback or enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + m_maxConcurrency, + AZStd::nullopt, + AZStd::nullopt); + + // Given a mixture of test enumeration jobs that execute and return either successfully or with failure + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second, AZStd::nullopt); + const AZStd::string args = (jobId % 2) + ? AZStd::string::format("%s %s", ValidProcessPath, ConstructTestProcessArgs(jobId, AZStd::chrono::milliseconds(0)).c_str()) + : m_testTargetJobArgs[jobId]; + m_jobInfos.emplace_back(JobInfo({jobId}, args, AZStd::move(jobData))); + } + + try + { + // When the test enumeration jobs are executed with different exception policies + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, CacheExceptionPolicy::Never, m_jobExceptionPolicy); + + // Expect this statement to be reachable only if no exception policy for jobs that return with error + EXPECT_FALSE(::IsFlagSet(m_jobExceptionPolicy, JobExceptionPolicy::OnExecutedWithFailure)); + + for (const auto& job : enumerationJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + if (jobId % 2) + { + // Expect failed jobs to have job result ExecutedWithFailure and a non-zero return code + ValidateJobExecutedWithFailure(job); + } + else + { + // Expect the valid jobs job to successfully result in a test enumeration that matches the expected test enumeration data + ValidateJobExecutedSuccessfully(job); + ValidateTestTargetEnumeration(job.GetPayload().value(), m_expectedTestTargetEnumerations[jobId]); + } + } + } + catch ([[maybe_unused]] const TestImpact::TestJobException& e) + { + // Expect this statement to be reachable only if there is an exception policy for jobs that return with error + EXPECT_TRUE(::IsFlagSet(m_jobExceptionPolicy, JobExceptionPolicy::OnExecutedWithFailure)); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_P(TestEnumeratorFixtureWithConcurrencyAndCacheExceptionParams, EmptyCacheRead_NoCacheDataButEnumerationsMatchTestSuitesInTarget) + { + // Given a test enumerator with no client callback, enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + m_maxConcurrency, + AZStd::nullopt, + AZStd::nullopt); + + // Given an test enumeration job for each test target that reads from an enumeration caching + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second, JobData::Cache{JobData::CachePolicy::Read, m_cacheFiles[jobId]}); + m_jobInfos.emplace_back(JobInfo({jobId}, m_testTargetJobArgs[jobId], AZStd::move(jobData))); + } + + try + { + // When the test enumeration jobs are executed with different exception policies + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, m_cacheExceptionPolicy, JobExceptionPolicy::Never); + + // Expect this statement to be reachable only if no exception policy for read attempts of non-existent caches + EXPECT_FALSE(::IsFlagSet(m_cacheExceptionPolicy, CacheExceptionPolicy::OnCacheNotExist)); + + // Expect each job to successfully result in a test enumeration that matches the expected test enumeration data for that test target + // even though the cache files could not be read + for (const auto& job : enumerationJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + ValidateJobExecutedSuccessfully(job); + ValidateTestTargetEnumeration(job.GetPayload().value(), m_expectedTestTargetEnumerations[jobId]); + ValidateInvalidTestEnumerationCache(job.GetJobInfo().GetCache()->m_file); + } + } + catch ([[maybe_unused]] const TestImpact::TestEnumerationException& e) + { + // Expect this statement to be reachable only if there is an exception policy for read attempts of non-existent caches + EXPECT_TRUE(::IsFlagSet(m_cacheExceptionPolicy, CacheExceptionPolicy::OnCacheNotExist)); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + // Note: this test only cues up one test for enumeration but still runs the permutations for max concurrency so there is duplicated work + TEST_P( + TestEnumeratorFixtureWithConcurrencyAndCacheExceptionParams, + EmptyCacheDataRead_ExpectEnumerationsMatchTestSuitesInTargetOrTestEnumerationException) + { + // Given an enumeration cache for Test Target A with invalid JSON data + WriteTextToFile("", m_cacheFiles[TestTargetA]); + + // Given a test enumerator with no client callback, enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + m_maxConcurrency, + AZStd::nullopt, + AZStd::nullopt); + + // Given an test enumeration job that will attempt to read an invalid enumeration cache + JobData jobData(m_testTargetPaths[TestTargetA].second, JobData::Cache{JobData::CachePolicy::Read, m_cacheFiles[TestTargetA]}); + m_jobInfos.emplace_back(JobInfo({TestTargetA}, m_testTargetJobArgs[TestTargetA], AZStd::move(jobData))); + + try + { + // When the test enumeration jobs are executed with different exception policies + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, m_cacheExceptionPolicy, JobExceptionPolicy::Never); + + // Expect this statement to be reachable only if no exception policy for cache reads that fail + EXPECT_FALSE(::IsFlagSet(m_cacheExceptionPolicy, CacheExceptionPolicy::OnCacheReadFailure)); + + for (const auto& job : enumerationJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + + // Expect the valid jobs job to successfully result in a test enumeration that matches the expected test enumeration data + ValidateJobExecutedSuccessfully(job); + ValidateTestTargetEnumeration(job.GetPayload().value(), m_expectedTestTargetEnumerations[jobId]); + } + } + catch ([[maybe_unused]] const TestImpact::TestEnumerationException& e) + { + // Expect this statement to be reachable only if there is an exception policy for cache reads that fail + EXPECT_TRUE(::IsFlagSet(m_cacheExceptionPolicy, CacheExceptionPolicy::OnCacheReadFailure)); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_P( + TestEnumeratorFixtureWithConcurrencyAndCacheExceptionParams, + InvalidCacheWrite_ExpectEnumerationsMatchTestSuitesInTargetOrTestEnumerationException) + { + // Given a test enumerator with no client callback,, enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + m_maxConcurrency, + AZStd::nullopt, + AZStd::nullopt); + + // Given an test enumeration job that will attempt to write to an invalid enumeration cache + JobData jobData(m_testTargetPaths[TestTargetA].second, JobData::Cache{JobData::CachePolicy::Write, InvalidProcessPath}); + m_jobInfos.emplace_back(JobInfo({TestTargetA}, m_testTargetJobArgs[TestTargetA], AZStd::move(jobData))); + + try + { + // When the test enumeration job is executed + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, m_cacheExceptionPolicy, JobExceptionPolicy::Never); + + // Expect this statement to be reachable only if no exception policy for cache writes that fail + EXPECT_FALSE(::IsFlagSet(m_cacheExceptionPolicy, CacheExceptionPolicy::OnCacheWriteFailure)); + + for (const auto& job : enumerationJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + + // Expect the valid jobs job to successfully result in a test enumeration that matches the expected test enumeration data + ValidateJobExecutedSuccessfully(job); + ValidateTestTargetEnumeration(job.GetPayload().value(), m_expectedTestTargetEnumerations[jobId]); + } + } + catch ([[maybe_unused]] const TestImpact::TestEnumerationException& e) + { + // Expect this statement to be reachable only if there is an exception policy for cache writes that fail + EXPECT_TRUE(::IsFlagSet(m_cacheExceptionPolicy, CacheExceptionPolicy::OnCacheWriteFailure)); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_P(TestEnumeratorFixtureWithConcurrencyParams, ValidAndInvalidCacheRead_CachedEnumerationsMatchTestSuitesInTarget) + { + // Given the cache file written for only test target B + WriteTextToFile(TestImpact::SerializeTestEnumeration(m_expectedTestTargetEnumerations[TestTargetB]), m_cacheFiles[TestTargetB]); + + // Given a test enumerator with no client callback, enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + m_maxConcurrency, + AZStd::nullopt, + AZStd::nullopt); + + // Given test enumeration jobs test target A and D with no enumeration caching + m_jobInfos.emplace_back( + JobInfo({TestTargetA}, m_testTargetJobArgs[TestTargetA], JobData{m_testTargetPaths[TestTargetA].second, AZStd::nullopt})); + m_jobInfos.emplace_back( + JobInfo({TestTargetD}, m_testTargetJobArgs[TestTargetD], JobData{m_testTargetPaths[TestTargetD].second, AZStd::nullopt})); + + // Given test target B with enumeration cache reading and a valid cache file + m_jobInfos.emplace_back(JobInfo( + {TestTargetB}, m_testTargetJobArgs[TestTargetB], + JobData{m_testTargetPaths[TestTargetB].second, JobInfo::Cache{JobData::CachePolicy::Read, m_cacheFiles[TestTargetB]}})); + + // Given test target C with enumeration cache reading and an invalid cache file + m_jobInfos.emplace_back(JobInfo( + {TestTargetC}, m_testTargetJobArgs[TestTargetC], + JobData{m_testTargetPaths[TestTargetC].second, JobInfo::Cache{JobData::CachePolicy::Read, "nothing"}})); + + // When the test enumeration jobs are executed + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, CacheExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Expect each job to successfully result in a test enumeration that matches the expected test enumeration data for that test target + for (const auto& job : enumerationJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + + switch (jobId) + { + case TestTargetA: // No cache read + case TestTargetC: // Cache read, but invalid cache, so re-enumerate anyway + case TestTargetD: // No cache read + { + ValidateJobExecutedSuccessfully(job); + break; + } + case TestTargetB: // Cache read, successful cache read, so job not executed + { + ValidateJobSuccessfulCacheRead(job); + break; + } + default: + { + FAIL(); + } + } + + // Regardless of cache policy and cache failures all targets should still produce the expected test enumerations + ValidateTestTargetEnumeration(job.GetPayload().value(), m_expectedTestTargetEnumerations[jobId]); + } + } + + TEST_F(TestEnumeratorFixture, InvalidCacheDataRead_TestEnumerationException) + { + // Given an enumeration cache for Test Target A with invalid JSON data + WriteTextToFile("There is no valid cache data here", m_cacheFiles[TestTargetA]); + + // Given a test enumerator with no client callback, concurrency, enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + OneConcurrentProcess, + AZStd::nullopt, + AZStd::nullopt); + + // Given an test enumeration job that will attempt to read an invalid enumeration cache + JobData jobData(m_testTargetPaths[TestTargetA].second, JobData::Cache{JobData::CachePolicy::Read, m_cacheFiles[TestTargetA]}); + m_jobInfos.emplace_back(JobInfo({TestTargetA}, m_testTargetJobArgs[TestTargetA], AZStd::move(jobData))); + + try + { + // When the test enumeration jobs are executed with different exception policies + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, CacheExceptionPolicy::Never, JobExceptionPolicy::Never); + } + catch ([[maybe_unused]] const TestImpact::TestEnumerationException& e) + { + // Expect this statement to be reachable only if there is an exception policy for jobs that return with error + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_P(TestEnumeratorFixtureWithConcurrencyParams, ValidCacheWrite_CachedEnumerationsMatchTestSuitesInTarget) + { + // Given a test enumerator with no client callback, enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + m_maxConcurrency, + AZStd::nullopt, + AZStd::nullopt); + + // Given an test enumeration job for each test target with write enumeration caching + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second, JobData::Cache{JobData::CachePolicy::Write, m_cacheFiles[jobId]}); + m_jobInfos.emplace_back(JobInfo({jobId}, m_testTargetJobArgs[jobId], AZStd::move(jobData))); + } + + // When the test enumeration jobs are executed + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, CacheExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Expect each job to successfully result in a test enumeration and cache that matches the expected test enumeration data for that + // test target + for (const auto& job : enumerationJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + ValidateJobExecutedSuccessfully(job); + ValidateTestTargetEnumeration(job.GetPayload().value(), m_expectedTestTargetEnumerations[jobId]); + ValidateTestEnumerationCache(job.GetJobInfo().GetCache()->m_file, m_expectedTestTargetEnumerations[jobId]); + } + } + + TEST_F(TestEnumeratorFixture, EmptyArtifact_ExpectTestEnumerationException) + { + // Given a test enumerator with no client callback, concurrency, enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + OneConcurrentProcess, + AZStd::nullopt, + AZStd::nullopt); + + // Given an test enumeration job that will return successfully but with an empty artifact string + m_jobInfos.emplace_back(JobInfo({0}, m_testTargetJobArgs[0], JobData("", AZStd::nullopt))); + + try + { + // When the test enumeration job is executed + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, CacheExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::TestEnumerationException& e) + { + // Expect an enumeration exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(TestEnumeratorFixture, InvalidArtifact_ExpectTestEnumerationException) + { + // Given a test enumerator with no client callback, concurrency, enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + OneConcurrentProcess, + AZStd::nullopt, + AZStd::nullopt); + + // Given an test enumeration job that will return successfully but not produce an artifact + m_jobInfos.emplace_back(JobInfo( + {0}, AZStd::string::format("%s %s", ValidProcessPath, ConstructTestProcessArgs(0, AZStd::chrono::milliseconds(0)).c_str()), + JobData("", AZStd::nullopt))); + + try + { + // When the test enumeration job is executed + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, CacheExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::TestEnumerationException& e) + { + // Expect an enumeration exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_P(TestEnumeratorFixtureWithConcurrencyParams, EnumerateTestTargets_EnumerationsMatchTestSuitesInTarget) + { + // Given a test enumerator with no client callback, enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + m_maxConcurrency, + AZStd::nullopt, + AZStd::nullopt); + + // Given an test enumeration job for each test target with no enumeration caching + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second, AZStd::nullopt); + m_jobInfos.emplace_back(JobInfo({jobId}, m_testTargetJobArgs[jobId], AZStd::move(jobData))); + } + + // When the test enumeration jobs are executed + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, CacheExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Expect each job to successfully result in a test enumeration that matches the expected test enumeration data for that test target + for (const auto& job : enumerationJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + ValidateJobExecutedSuccessfully(job); + ValidateTestTargetEnumeration(job.GetPayload().value(), m_expectedTestTargetEnumerations[jobId]); + } + } + + TEST_P(TestEnumeratorFixtureWithConcurrencyParams, EnumerateTestTargetsWithArbitraryJobIds_EnumerationsMatchTestSuitesInTarget) + { + // Given a set of arbitrary job ids to be used for the test target jobs + enum + { + ArbitraryA = 36, + ArbitraryB = 890, + ArbitraryC = 19, + ArbitraryD = 1 + }; + + const AZStd::unordered_map sequentialToArbitrary = + { + {TestTargetA, ArbitraryA}, + {TestTargetB, ArbitraryB}, + {TestTargetC, ArbitraryC}, + {TestTargetD, ArbitraryD}, + }; + + const AZStd::unordered_map arbitraryToSequential = + { + {ArbitraryA, TestTargetA}, + {ArbitraryB, TestTargetB}, + {ArbitraryC, TestTargetC}, + {ArbitraryD, TestTargetD}, + }; + + // Given a test enumerator with no client callback, enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + m_maxConcurrency, + AZStd::nullopt, + AZStd::nullopt); + + // Given an test enumeration job for each test target with no enumeration caching + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second, AZStd::nullopt); + m_jobInfos.emplace_back(JobInfo({sequentialToArbitrary.at(jobId)}, m_testTargetJobArgs[jobId], AZStd::move(jobData))); + } + + // When the test enumeration jobs are executed + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, CacheExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Expect each job to successfully result in a test enumeration that matches the expected test enumeration data for that test target + for (const auto& job : enumerationJobs) + { + const JobInfo::IdType jobId = arbitraryToSequential.at(job.GetJobInfo().GetId().m_value); + ValidateJobExecutedSuccessfully(job); + ValidateTestTargetEnumeration(job.GetPayload().value(), m_expectedTestTargetEnumerations[jobId]); + } + } + + TEST_P(TestEnumeratorFixtureWithConcurrencyParams, EnumerateTestTargetsWithCallback_EnumerationsMatchTestSuitesInTarget) + { + // Given a client callback function that tracks the number of successful enumerations + size_t numSuccesses = 0; + const auto jobCallback = [&numSuccesses]([[maybe_unused]] const TestImpact::TestEnumerator::JobInfo& jobInfo, const TestImpact::JobMeta& meta) + { + if (meta.m_result == TestImpact::JobResult::ExecutedWithSuccess) + { + numSuccesses++; + } + }; + + // Given a test enumerator with no enumeration timeout or enumerator timeout + m_testEnumerator = AZStd::make_unique( + jobCallback, + m_maxConcurrency, + AZStd::nullopt, + AZStd::nullopt); + + // Given an test enumeration job for each test target with no enumeration caching + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second, AZStd::nullopt); + m_jobInfos.emplace_back(JobInfo({jobId}, m_testTargetJobArgs[jobId], AZStd::move(jobData))); + } + + // When the test enumeration jobs are executed + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, CacheExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Expect the number of successful enumerations tracked in the callback to match the number of test targets enumerated + EXPECT_EQ(numSuccesses, enumerationJobs.size()); + + // Expect each job to successfully result in a test enumeration that matches the expected test enumeration data for that test target + for (const auto& job : enumerationJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + ValidateJobExecutedSuccessfully(job); + ValidateTestTargetEnumeration(job.GetPayload().value(), m_expectedTestTargetEnumerations[jobId]); + } + } + + TEST_P(TestEnumeratorFixtureWithConcurrencyParams, JobRunnerTimeout_InFlightJobsTimeoutAndQueuedJobsUnlaunched) + { + // Given a test enumerator with no client callback or enumerator timeout and 500ms enumeration timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + m_maxConcurrency, + AZStd::chrono::milliseconds(500), + AZStd::nullopt); + + // Given an test enumeration job for each test target with no enumeration caching where half will sleep indefinitely + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second, AZStd::nullopt); + const AZStd::string args = (jobId % 2) + ? AZStd::string::format("%s %s", ValidProcessPath, ConstructTestProcessArgs(jobId, LongSleep).c_str()) + : m_testTargetJobArgs[jobId]; + m_jobInfos.emplace_back(JobInfo({jobId}, args, AZStd::move(jobData))); + } + + // When the test enumeration jobs are executed + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, CacheExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Expect half the jobs to successfully result in a test enumeration that matches the expected test enumeration data for that test + // target with the other half having timed out + for (const auto& job : enumerationJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + if (jobId % 2) + { + ValidateJobTimeout(job); + } + else + { + ValidateJobExecutedSuccessfully(job); + ValidateTestTargetEnumeration(job.GetPayload().value(), m_expectedTestTargetEnumerations[jobId]); + } + } + } + + TEST_F(TestEnumeratorFixture, JobTimeout_InFlightJobTimeoutAndQueuedJobsUnlaunched) + { + // Given a test enumerator with no client callback or enumeration timeout and a 5 second enumerator timeout + m_testEnumerator = AZStd::make_unique( + AZStd::nullopt, + FourConcurrentProcesses, + AZStd::nullopt, + AZStd::chrono::milliseconds(5000)); + + // Given an test enumeration job for each test target with no enumeration caching where half will sleep indefinitely + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second, AZStd::nullopt); + const AZStd::string args = (jobId % 2) + ? AZStd::string::format("%s %s", ValidProcessPath, ConstructTestProcessArgs(jobId, LongSleep).c_str()) + : m_testTargetJobArgs[jobId]; + m_jobInfos.emplace_back(JobInfo({jobId}, args, AZStd::move(jobData))); + } + + // When the test enumeration jobs are executed + const auto enumerationJobs = m_testEnumerator->Enumerate(m_jobInfos, CacheExceptionPolicy::Never, JobExceptionPolicy::Never); + + // Expect half the jobs to successfully result in a test enumeration that matches the expected test enumeration data for that test + // target with the other half having timed out + for (const auto& job : enumerationJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + if (jobId % 2) + { + ValidateJobTimeout(job); + } + else + { + ValidateJobExecutedSuccessfully(job); + ValidateTestTargetEnumeration(job.GetPayload().value(), m_expectedTestTargetEnumerations[jobId]); + } + } + } + + INSTANTIATE_TEST_CASE_P( + , + TestEnumeratorFixtureWithConcurrencyAndJobExceptionParams, + ::testing::Combine( + ::testing::ValuesIn(MaxConcurrentEnumerations), + ::testing::ValuesIn(JobExceptionPolicies)) + ); + + INSTANTIATE_TEST_CASE_P( + , + TestEnumeratorFixtureWithConcurrencyAndCacheExceptionParams, + ::testing::Combine( + ::testing::ValuesIn(MaxConcurrentEnumerations), + ::testing::ValuesIn(CacheExceptionPolicies)) + ); + + INSTANTIATE_TEST_CASE_P( + , + TestEnumeratorFixtureWithConcurrencyParams, + ::testing::ValuesIn(MaxConcurrentEnumerations) + ); +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestEumerationSerializerTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestEumerationSerializerTest.cpp new file mode 100644 index 0000000000..aa94657378 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestEumerationSerializerTest.cpp @@ -0,0 +1,99 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include + +#include +#include + +namespace UnitTest +{ + const TestImpact::TestEnumeration EmptyTestEnum(AZStd::vector{}); + + TEST(TestEnumerationSerializer, EmptyTestEnumerationSuites_ExpectTemptyTestEnumeration) + { + // Given an empty set of test enumeration suites + // When the test enumeration is serialized and deserialized back again + const AZStd::string serializedString = TestImpact::SerializeTestEnumeration(EmptyTestEnum); + const TestImpact::TestEnumeration actualEnumeration = TestImpact::DeserializeTestEnumeration(serializedString); + + // Expect the actual test enumeration to match the expected test enumeration + EXPECT_TRUE(actualEnumeration == EmptyTestEnum); + } + + TEST(TestEnumerationSerializer, SerializeAndDeserializeSuitesForTestTargetA_ActualSuiteDataMatchesExpectedSuiteData) + { + // Given the test of test enumeration suites for Test target A + const TestImpact::TestEnumeration expectedEnumeration = TestImpact::TestEnumeration(GetTestTargetATestEnumerationSuites()); + + // When the test enumeration is serialized and deserialized back again + const AZStd::string serializedString = TestImpact::SerializeTestEnumeration(expectedEnumeration); + const TestImpact::TestEnumeration actualEnumeration = TestImpact::DeserializeTestEnumeration(serializedString); + + // Expect the actual test enumeration to match the expected test enumeration + EXPECT_TRUE(actualEnumeration == expectedEnumeration); + + // Do not expect the actual test enumeration to match the empty test enumeration + EXPECT_FALSE(actualEnumeration == EmptyTestEnum); + } + + TEST(TestEnumerationSerializer, SerializeAndDeserializeSuitesForTestTargetB_ActualSuiteDataMatchesExpectedSuiteData) + { + // Given the test of test enumeration suites for Test target B + const TestImpact::TestEnumeration expectedEnumeration = TestImpact::TestEnumeration(GetTestTargetBTestEnumerationSuites()); + + // When the test enumeration is serialized and deserialized back again + const AZStd::string serializedString = TestImpact::SerializeTestEnumeration(expectedEnumeration); + const TestImpact::TestEnumeration actualEnumeration = TestImpact::DeserializeTestEnumeration(serializedString); + + // Expect the actual test enumeration to match the expected test enumeration + EXPECT_TRUE(actualEnumeration == expectedEnumeration); + + // Do not expect the actual test enumeration to match the empty test enumeration + EXPECT_FALSE(actualEnumeration == EmptyTestEnum); + } + + TEST(TestEnumerationSerializer, SerializeAndDeserializeSuitesForTestTargetC_ActualSuiteDataMatchesExpectedSuiteData) + { + // Given the test of test enumeration suites for Test target B + const TestImpact::TestEnumeration expectedEnumeration = TestImpact::TestEnumeration(GetTestTargetCTestEnumerationSuites()); + + // When the test enumeration is serialized and deserialized back again + const AZStd::string serializedString = TestImpact::SerializeTestEnumeration(expectedEnumeration); + const TestImpact::TestEnumeration actualEnumeration = TestImpact::DeserializeTestEnumeration(serializedString); + + // Expect the actual test enumeration to match the expected test enumeration + EXPECT_TRUE(actualEnumeration == expectedEnumeration); + + // Do not expect the actual test enumeration to match the empty test enumeration + EXPECT_FALSE(actualEnumeration == EmptyTestEnum); + } + + TEST(TestEnumerationSerializer, SerializeAndDeserializeSuitesForTestTargetD_ActualSuiteDataMatchesExpectedSuiteData) + { + // Given the test of test enumeration suites for Test target B + const TestImpact::TestEnumeration expectedEnumeration = TestImpact::TestEnumeration(GetTestTargetDTestEnumerationSuites()); + + // When the test enumeration is serialized and deserialized back again + const AZStd::string serializedString = TestImpact::SerializeTestEnumeration(expectedEnumeration); + const TestImpact::TestEnumeration actualEnumeration = TestImpact::DeserializeTestEnumeration(serializedString); + + // Expect the actual test enumeration to match the expected test enumeration + EXPECT_TRUE(actualEnumeration == expectedEnumeration); + + // Do not expect the actual test enumeration to match the empty test enumeration + EXPECT_FALSE(actualEnumeration == EmptyTestEnum); + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestRunSerializerTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestRunSerializerTest.cpp new file mode 100644 index 0000000000..1fc7c62b3d --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestRunSerializerTest.cpp @@ -0,0 +1,99 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include + +#include +#include + +namespace UnitTest +{ + const TestImpact::TestRun EmptyTestRun(AZStd::vector{}, AZStd::chrono::milliseconds{0}); + + TEST(TestRunSerializer, EmptyTestRunSuites_ExpectTemptyTestRun) + { + // Given an empty set of test run suites + // When the test run is serialized and deserialized back again + const auto serializedString = TestImpact::SerializeTestRun(EmptyTestRun); + const auto actualRun = TestImpact::DeserializeTestRun(serializedString); + + // Expect the actual test run to match the expected test run + EXPECT_TRUE(actualRun == EmptyTestRun); + } + + TEST(TestRunSerializer, SerializeAndDeserializeSuitesForTestTargetA_ActualSuiteDataMatchesExpectedSuiteData) + { + // Given the test of test run suites for Test target A + const auto expectedRun = TestImpact::TestRun(GetTestTargetATestRunSuites(), AZStd::chrono::milliseconds{500}); + + // When the test run is serialized and deserialized back again + const auto serializedString = TestImpact::SerializeTestRun(expectedRun); + const auto actualRun = TestImpact::DeserializeTestRun(serializedString); + + // Expect the actual test run to match the expected test run + EXPECT_TRUE(actualRun == expectedRun); + + // Do not expect the actual test run to match the empty test run + EXPECT_FALSE(actualRun == EmptyTestRun); + } + + TEST(TestRunSerializer, SerializeAndDeserializeSuitesForTestTargetB_ActualSuiteDataMatchesExpectedSuiteData) + { + // Given the test of test run suites for Test target B + const auto expectedRun = TestImpact::TestRun(GetTestTargetBTestRunSuites(), AZStd::chrono::milliseconds{500}); + + // When the test run is serialized and deserialized back again + const auto serializedString = TestImpact::SerializeTestRun(expectedRun); + const auto actualRun = TestImpact::DeserializeTestRun(serializedString); + + // Expect the actual test run to match the expected test run + EXPECT_TRUE(actualRun == expectedRun); + + // Do not expect the actual test run to match the empty test run + EXPECT_FALSE(actualRun == EmptyTestRun); + } + + TEST(TestRunSerializer, SerializeAndDeserializeSuitesForTestTargetC_ActualSuiteDataMatchesExpectedSuiteData) + { + // Given the test of test run suites for Test target B + const auto expectedRun = TestImpact::TestRun(GetTestTargetCTestRunSuites(), AZStd::chrono::milliseconds{500}); + + // When the test run is serialized and deserialized back again + const auto serializedString = TestImpact::SerializeTestRun(expectedRun); + const auto actualRun = TestImpact::DeserializeTestRun(serializedString); + + // Expect the actual test run to match the expected test run + EXPECT_TRUE(actualRun == expectedRun); + + // Do not expect the actual test run to match the empty test run + EXPECT_FALSE(actualRun == EmptyTestRun); + } + + TEST(TestRunSerializer, SerializeAndDeserializeSuitesForTestTargetD_ActualSuiteDataMatchesExpectedSuiteData) + { + // Given the test of test run suites for Test target B + const auto expectedRun = TestImpact::TestRun(GetTestTargetDTestRunSuites(), AZStd::chrono::milliseconds{500}); + + // When the test run is serialized and deserialized back again + const auto serializedString = TestImpact::SerializeTestRun(expectedRun); + const auto actualRun = TestImpact::DeserializeTestRun(serializedString); + + // Expect the actual test run to match the expected test run + EXPECT_TRUE(actualRun == expectedRun); + + // Do not expect the actual test run to match the empty test run + EXPECT_FALSE(actualRun == EmptyTestRun); + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestRunnerTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestRunnerTest.cpp new file mode 100644 index 0000000000..08a0306e7f --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/Test/TestImpactTestRunnerTest.cpp @@ -0,0 +1,516 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + using JobExceptionPolicy = TestImpact::TestRunner::JobExceptionPolicy; + + AZStd::string GetRunCommandForTarget(AZStd::pair testTarget) + { + return AZStd::string::format( + "%s %s AzRunUnitTests --gtest_output=xml:%s", LY_TEST_IMPACT_AZ_TESTRUNNER_BIN, testTarget.first.c_str(), + testTarget.second.c_str()); + } + + class TestRunnerFixture + : public AllocatorsTestFixture + { + public: + void SetUp() override; + + protected: + using JobInfo = TestImpact::TestRunner::JobInfo; + using JobData = TestImpact::TestRunner::JobData; + + AZStd::vector m_jobInfos; + AZStd::unique_ptr m_testRunner; + AZStd::vector m_testTargetJobArgs; + AZStd::vector> m_testTargetPaths; + AZStd::vector m_expectedTestTargetRuns; + AZStd::vector m_expectedTestTargetResult; + size_t m_maxConcurrency = 0; + }; + + void TestRunnerFixture::SetUp() + { + UnitTest::AllocatorsTestFixture::SetUp(); + + DeleteFiles(LY_TEST_IMPACT_TEST_TARGET_RESULTS_DIR, "*.xml"); + + // first: path to test target bin + // second: path to test target gtest results file in XML format + const AZStd::string runPath = AZStd::string(LY_TEST_IMPACT_TEST_TARGET_RESULTS_DIR) + "/%s.Run.xml"; + m_testTargetPaths.emplace_back( + LY_TEST_IMPACT_TEST_TARGET_A_BIN, AZStd::string::format(runPath.c_str(), LY_TEST_IMPACT_TEST_TARGET_A_BASE_NAME)); + m_testTargetPaths.emplace_back( + LY_TEST_IMPACT_TEST_TARGET_B_BIN, AZStd::string::format(runPath.c_str(), LY_TEST_IMPACT_TEST_TARGET_B_BASE_NAME)); + m_testTargetPaths.emplace_back( + LY_TEST_IMPACT_TEST_TARGET_C_BIN, AZStd::string::format(runPath.c_str(), LY_TEST_IMPACT_TEST_TARGET_C_BASE_NAME)); + m_testTargetPaths.emplace_back( + LY_TEST_IMPACT_TEST_TARGET_D_BIN, AZStd::string::format(runPath.c_str(), LY_TEST_IMPACT_TEST_TARGET_D_BASE_NAME)); + + m_expectedTestTargetRuns.emplace_back(GetTestTargetATestRunSuites(), AZStd::chrono::milliseconds{500}); + m_expectedTestTargetRuns.emplace_back(GetTestTargetBTestRunSuites(), AZStd::chrono::milliseconds{500}); + m_expectedTestTargetRuns.emplace_back(GetTestTargetCTestRunSuites(), AZStd::chrono::milliseconds{500}); + m_expectedTestTargetRuns.emplace_back(GetTestTargetDTestRunSuites(), AZStd::chrono::milliseconds{500}); + + m_expectedTestTargetResult.emplace_back(TestImpact::TestRunResult::Failed); + m_expectedTestTargetResult.emplace_back(TestImpact::TestRunResult::Passed); + m_expectedTestTargetResult.emplace_back(TestImpact::TestRunResult::Passed); + m_expectedTestTargetResult.emplace_back(TestImpact::TestRunResult::Passed); + + for (const auto& testTarget : m_testTargetPaths) + { + m_testTargetJobArgs.emplace_back(GetRunCommandForTarget(testTarget)); + } + } + + // Fixture parameterized for different max number of concurrent jobs + class TestRunnerFixtureWithConcurrencyParams + : public TestRunnerFixture + , public ::testing::WithParamInterface + { + public: + void SetUp() override + { + TestRunnerFixture::SetUp(); + m_maxConcurrency = GetParam(); + } + }; + + using ConcurrencyAndJobExceptionPermutation = AZStd::tuple + < + size_t, // Max number of concurrent processes + JobExceptionPolicy // Test job exception policy + >; + + // Fixture parameterized for different max number of concurrent jobs and different job exception policies + class TestRunnerFixtureWithConcurrencyAndJobExceptionParams + : public TestRunnerFixture + , public ::testing::WithParamInterface + { + public: + void SetUp() override + { + TestRunnerFixture::SetUp(); + const auto& [maxConcurrency, jobExceptionPolicy] = GetParam(); + m_maxConcurrency = maxConcurrency; + m_jobExceptionPolicy = jobExceptionPolicy; + } + + protected: + JobExceptionPolicy m_jobExceptionPolicy = JobExceptionPolicy::Never; + }; + + class TestRunnerFixtureWithConcurrencyAndFailedToLaunchExceptionParams + : public TestRunnerFixtureWithConcurrencyAndJobExceptionParams + { + }; + + class TestRunnerFixtureWithConcurrencyAndExecutedWithFailureExceptionParams + : public TestRunnerFixtureWithConcurrencyAndJobExceptionParams + { + }; + + namespace + { + AZStd::array MaxConcurrentRuns = {1, 2, 3, 4}; + + AZStd::array FailedToLaunchExceptionPolicies = { + JobExceptionPolicy::Never, JobExceptionPolicy::OnFailedToExecute}; + + AZStd::array ExecutedWithFailureExceptionPolicies = { + JobExceptionPolicy::Never, JobExceptionPolicy::OnExecutedWithFailure}; + } // namespace + + TEST_P( + TestRunnerFixtureWithConcurrencyAndFailedToLaunchExceptionParams, + InvalidCommandArgument_ExpectJobResulFailedToExecuteeOrTestJobException) + { + // Given a test runner with no client callback or run timeout or runner timeout + m_testRunner = AZStd::make_unique(AZStd::nullopt, m_maxConcurrency, AZStd::nullopt, AZStd::nullopt); + + // Given a mixture of test run jobs with valid and invalid command arguments + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + const AZStd::string args = (jobId % 2) ? InvalidProcessPath : m_testTargetJobArgs[jobId]; + JobData jobData(m_testTargetPaths[jobId].second); + m_jobInfos.emplace_back(JobInfo({jobId}, args, AZStd::move(jobData))); + } + + try + { + // When the test run jobs are executed with different exception policies + const auto runnerJobs = m_testRunner->RunTests(m_jobInfos, m_jobExceptionPolicy); + + // Expect this statement to be reachable only if no exception policy for launch failures + EXPECT_FALSE(::IsFlagSet(m_jobExceptionPolicy, JobExceptionPolicy::OnFailedToExecute)); + + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + if (jobId % 2) + { + // Expect invalid jobs have a job result of FailedToExecute + ValidateJobFailedToExecute(job); + } + else + { + // Expect the valid jobs to successfully result in a test run that matches the expected test run data + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value(), m_expectedTestTargetRuns[jobId]); + } + } + } + catch ([[maybe_unused]] const TestImpact::TestJobException& e) + { + // Expect this statement to be reachable only if there is an exception policy for launch failures + EXPECT_TRUE(::IsFlagSet(m_jobExceptionPolicy, JobExceptionPolicy::OnFailedToExecute)); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_P( + TestRunnerFixtureWithConcurrencyAndExecutedWithFailureExceptionParams, + ErroneousReturnCode_ExpectJobResultExecutedWithFailureOrTestJobException) + { + // Given a test runner with no client callback or run timeout or runner timeout + m_testRunner = AZStd::make_unique(AZStd::nullopt, m_maxConcurrency, AZStd::nullopt, AZStd::nullopt); + + // Given a mixture of test run jobs that execute and return either successfully or with failure + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second); + m_jobInfos.emplace_back(JobInfo({jobId}, m_testTargetJobArgs[jobId], AZStd::move(jobData))); + } + + try + { + // When the test run jobs are executed with different exception policies + const auto runnerJobs = m_testRunner->RunTests(m_jobInfos, m_jobExceptionPolicy); + + // Expect this statement to be reachable only if no exception policy for jobs that return with error + EXPECT_FALSE(::IsFlagSet(m_jobExceptionPolicy, JobExceptionPolicy::OnExecutedWithFailure)); + + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + + // Expect the valid jobs to successfully result in a test run that matches the expected test run data + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value(), m_expectedTestTargetRuns[jobId]); + } + } + catch ([[maybe_unused]] const TestImpact::TestJobException& e) + { + // Expect this statement to be reachable only if there is an exception policy for jobs that return with error + EXPECT_TRUE(::IsFlagSet(m_jobExceptionPolicy, JobExceptionPolicy::OnExecutedWithFailure)); + } + catch ([[maybe_unused]] const TestImpact::Exception& e) + { + FAIL(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(TestRunnerFixture, EmptyArtifact_ExpectTestRunnerException) + { + // Given a test runner with no client callback, concurrency, run timeout or runner timeout + m_testRunner = AZStd::make_unique(AZStd::nullopt, OneConcurrentProcess, AZStd::nullopt, AZStd::nullopt); + + // Given an test runner job that will return successfully but with an empty artifact string + m_jobInfos.emplace_back(JobInfo({0}, m_testTargetJobArgs[0], JobData(""))); + + try + { + // When the test runner job is executed + const auto runnerJobs = m_testRunner->RunTests(m_jobInfos, JobExceptionPolicy::Never); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::TestRunException& e) + { + // Expect an runner exception + SUCCEED(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_F(TestRunnerFixture, InvalidRunArtifact_ExpectArtifactException) + { + // Given a test run artifact with invalid contents + WriteTextToFile("There is nothing valid here", m_testTargetPaths[TestTargetA].second); + + // Given a job command that will write the test run artifact to a different location that what we will read from + auto invalidRunArtifact = m_testTargetPaths[TestTargetA]; + invalidRunArtifact.second /= ".xml"; + const AZStd::string args = GetRunCommandForTarget(invalidRunArtifact); + + // Given a test runner with no client callback, concurrency, run timeout or runner timeout + m_testRunner = AZStd::make_unique(AZStd::nullopt, OneConcurrentProcess, AZStd::nullopt, AZStd::nullopt); + + // Given an test runner job that will return successfully but not produce an artifact + JobData jobData(m_testTargetPaths[TestTargetA].second); + m_jobInfos.emplace_back(JobInfo({TestTargetA}, args, AZStd::move(jobData))); + + try + { + // When the test runner job is executed + const auto runnerJobs = m_testRunner->RunTests(m_jobInfos, JobExceptionPolicy::Never); + + // Do not expect this statement to be reachable + FAIL(); + } + catch ([[maybe_unused]] const TestImpact::ArtifactException& e) + { + // Expect an runner exception + SUCCEED(); + } + catch (const TestImpact::Exception& e) + { + std::cout << e.what(); + FAIL(); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST_P(TestRunnerFixtureWithConcurrencyParams, RunTestTargets_RunsMatchTestSuitesInTarget) + { + // Given a test runner with no client callback, runner timeout or runner timeout + m_testRunner = AZStd::make_unique(AZStd::nullopt, m_maxConcurrency, AZStd::nullopt, AZStd::nullopt); + + // Given an test runner job for each test target with no runner caching + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second); + m_jobInfos.emplace_back(JobInfo({jobId}, m_testTargetJobArgs[jobId], AZStd::move(jobData))); + } + + // When the test runner jobs are executed + const auto runnerJobs = m_testRunner->RunTests(m_jobInfos, JobExceptionPolicy::Never); + + // Expect each job to successfully result in a test runner that matches the expected test runner data for that test target + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value(), m_expectedTestTargetRuns[jobId]); + } + } + + TEST_P(TestRunnerFixtureWithConcurrencyParams, RunTestTargetsWithArbitraryJobIds_RunsMatchTestSuitesInTarget) + { + // Given a set of arbitrary job ids to be used for the test target jobs + enum + { + ArbitraryA = 36, + ArbitraryB = 890, + ArbitraryC = 19, + ArbitraryD = 1 + }; + + const AZStd::unordered_map sequentialToArbitrary = + { + {TestTargetA, ArbitraryA}, + {TestTargetB, ArbitraryB}, + {TestTargetC, ArbitraryC}, + {TestTargetD, ArbitraryD}, + }; + + const AZStd::unordered_map arbitraryToSequential = + { + {ArbitraryA, TestTargetA}, + {ArbitraryB, TestTargetB}, + {ArbitraryC, TestTargetC}, + {ArbitraryD, TestTargetD}, + }; + + // Given a test runner with no client callback, run timeout or runner timeout + m_testRunner = AZStd::make_unique(AZStd::nullopt, m_maxConcurrency, AZStd::nullopt, AZStd::nullopt); + + // Given an test run job for each test target + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second); + m_jobInfos.emplace_back(JobInfo({sequentialToArbitrary.at(jobId)}, m_testTargetJobArgs[jobId], AZStd::move(jobData))); + } + + // When the test run jobs are executed + const auto runnerJobs = m_testRunner->RunTests(m_jobInfos, JobExceptionPolicy::Never); + + // Expect each job to successfully result in a test run that matches the expected test run data for that test target + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = arbitraryToSequential.at(job.GetJobInfo().GetId().m_value); + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value(), m_expectedTestTargetRuns[jobId]); + } + } + + TEST_P(TestRunnerFixtureWithConcurrencyParams, RunTestTargetsWithCallback_RunsMatchTestSuitesInTarget) + { + // Given a client callback function that tracks the number of successful runs + size_t numSuccesses = 0; + const auto jobCallback = + [&numSuccesses]([[maybe_unused]] const TestImpact::TestRunner::JobInfo& jobInfo, const TestImpact::JobMeta& meta) { + if (meta.m_result == TestImpact::JobResult::ExecutedWithSuccess) + { + numSuccesses++; + } + }; + + // Given a test runner with no run timeout or runner timeout + m_testRunner = AZStd::make_unique(jobCallback, m_maxConcurrency, AZStd::nullopt, AZStd::nullopt); + + // Given an test run job for each test target + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second); + m_jobInfos.emplace_back(JobInfo({jobId}, m_testTargetJobArgs[jobId], AZStd::move(jobData))); + } + + // When the test run jobs are executed + const auto runnerJobs = m_testRunner->RunTests(m_jobInfos, JobExceptionPolicy::Never); + + // Expect the number of successful runs tracked in the callback to match the number of test targets run with no failures + EXPECT_EQ( + numSuccesses, + AZStd::count_if(m_expectedTestTargetResult.begin(), m_expectedTestTargetResult.end(), [](TestImpact::TestRunResult result) { + return result == TestImpact::TestRunResult::Passed; + })); + + // Expect each job to successfully result in a test run that matches the expected test run data for that test target + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value(), m_expectedTestTargetRuns[jobId]); + } + } + + TEST_P(TestRunnerFixtureWithConcurrencyParams, JobRunnerTimeout_InFlightJobsTimeoutAndQueuedJobsUnlaunched) + { + // Given a test runner with no client callback or runner timeout and 500ms run timeout + m_testRunner = + AZStd::make_unique(AZStd::nullopt, m_maxConcurrency, AZStd::chrono::milliseconds(500), AZStd::nullopt); + + // Given an test run job for each test target where half will sleep indefinitely + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second); + const AZStd::string args = (jobId % 2) + ? AZStd::string::format("%s %s", ValidProcessPath, ConstructTestProcessArgs(jobId, LongSleep).c_str()) + : m_testTargetJobArgs[jobId]; + m_jobInfos.emplace_back(JobInfo({jobId}, args, AZStd::move(jobData))); + } + + // When the test run jobs are executed + const auto runnerJobs = m_testRunner->RunTests(m_jobInfos, JobExceptionPolicy::Never); + + // Expect half the jobs to successfully result in a test run that matches the expected test run data for that test target + // with the other half having timed out + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + if (jobId % 2) + { + ValidateJobTimeout(job); + } + else + { + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value(), m_expectedTestTargetRuns[jobId]); + } + } + } + + TEST_F(TestRunnerFixture, JobTimeout_InFlightJobTimeoutAndQueuedJobsUnlaunched) + { + // Given a test runner with no client callback or run timeout and a 5 second runner timeout + m_testRunner = AZStd::make_unique( + AZStd::nullopt, FourConcurrentProcesses, AZStd::nullopt, AZStd::chrono::milliseconds(5000)); + + // Given an test run job for each test target where half will sleep indefinitely + for (size_t jobId = 0; jobId < m_testTargetJobArgs.size(); jobId++) + { + JobData jobData(m_testTargetPaths[jobId].second); + const AZStd::string args = (jobId % 2) + ? AZStd::string::format("%s %s", ValidProcessPath, ConstructTestProcessArgs(jobId, LongSleep).c_str()) + : m_testTargetJobArgs[jobId]; + m_jobInfos.emplace_back(JobInfo({jobId}, args, AZStd::move(jobData))); + } + + // When the test run jobs are executed + const auto runnerJobs = m_testRunner->RunTests(m_jobInfos, JobExceptionPolicy::Never); + + // Expect half the jobs to successfully result in a test run that matches the expected test run data for that test target + // with the other half having timed out + for (const auto& job : runnerJobs) + { + const JobInfo::IdType jobId = job.GetJobInfo().GetId().m_value; + if (jobId % 2) + { + ValidateJobTimeout(job); + } + else + { + ValidateTestRunCompleted(job, m_expectedTestTargetResult[jobId]); + ValidateTestTargetRun(job.GetPayload().value(), m_expectedTestTargetRuns[jobId]); + } + } + } + + INSTANTIATE_TEST_CASE_P( + , + TestRunnerFixtureWithConcurrencyAndFailedToLaunchExceptionParams, + ::testing::Combine(::testing::ValuesIn(MaxConcurrentRuns), ::testing::ValuesIn(FailedToLaunchExceptionPolicies))); + + INSTANTIATE_TEST_CASE_P( + , + TestRunnerFixtureWithConcurrencyAndExecutedWithFailureExceptionParams, + ::testing::Combine(::testing::ValuesIn(MaxConcurrentRuns), ::testing::ValuesIn(ExecutedWithFailureExceptionPolicies))); + + INSTANTIATE_TEST_CASE_P(, TestRunnerFixtureWithConcurrencyParams, ::testing::ValuesIn(MaxConcurrentRuns)); +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactExceptionTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactExceptionTest.cpp new file mode 100644 index 0000000000..bf2ccb6bc7 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactExceptionTest.cpp @@ -0,0 +1,150 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include + +namespace UnitTest +{ + constexpr const char* Message = "String Constructor"; + + TEST(ExceptionTest, DefaultConstructor_HasEmptyMessageString) + { + // Given an exception instantiated with the default constructor + const TestImpact::Exception e; + + // Expect the message to be an empty string + EXPECT_STREQ(e.what(), "\0"); + } + + TEST(ExceptionTest, StringConstructor_HasSpecifiedMessageString) + { + // Given an exception instantiated with a string + const AZStd::string msg(Message); + const TestImpact::Exception e(msg); + + // Expect the message to be the specified string + EXPECT_STREQ(e.what(), Message); + } + + TEST(ExceptionTest, StringLiteralConstructor_HasSpecifiedMessageString) + { + // Given an exception instantiated with a string literal + const TestImpact::Exception e(Message); + + // Expect the message to be the specified string + EXPECT_STREQ(e.what(), Message); + } + + TEST(ExceptionTest, InitializedWithLocalString_HasCopyOfLocalStringString) + { + try + { + // Given an exception instantiated with a string in local scope + throw(TestImpact::Exception(AZStd::string::format("%s", Message))); + + // Do not expect this code to be reachable + FAIL(); + } + catch (const TestImpact::Exception& e) + { + // Expect the message to be the specified out-of-scope string + EXPECT_STREQ(e.what(), Message); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(ExceptionTest, InitializedWithLocalStringLiteral_HasCopyOfLocalStringString) + { + try + { + // Given an exception instantiated with a copy of the message string in local scope + throw(TestImpact::Exception(AZStd::string::format("%s", Message).c_str())); + + // Do not expect this code to be reachable + FAIL(); + } + catch (const TestImpact::Exception& e) + { + // Expect the message to be the specified out-of-scope string literal + EXPECT_STREQ(e.what(), Message); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(ExceptionTest, EvalMacroFails_ExceptionTestThrown) + { + try + { + // Given an exception instantiated with a string literal in local scope + AZ_TestImpact_Eval(false, TestImpact::Exception, Message); + + // Do not expect this code to be reachable + FAIL(); + } + catch (const TestImpact::Exception& e) + { + // Expect the message contain the specified string + EXPECT_STREQ(e.what(), Message); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } + + TEST(ExceptionTest, EvalMacroSucceeds_ExceptionTestNotThrown) + { + try + { + // Given an exception instantiated with a string literal in local scope + AZ_TestImpact_Eval(true, TestImpact::Exception, Message); + + // Expect this code to be reachable + SUCCEED(); + } + catch (...) + { + // Do not expect any exceptions + FAIL(); + } + } + + TEST(ExceptionTest, Throw_ExceptionTestThrown) + { + try + { + // Given an exception instantiated with a string literal in local scope + throw(TestImpact::Exception(Message)); + } + catch (const TestImpact::Exception& e) + { + // Expect the message contain the specified string + EXPECT_STREQ(e.what(), Message); + } + catch (...) + { + // Do not expect any other exceptions + FAIL(); + } + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactFrameworkPathTest.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactFrameworkPathTest.cpp new file mode 100644 index 0000000000..2982cb0551 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactFrameworkPathTest.cpp @@ -0,0 +1,137 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include + +namespace UnitTest +{ + class FrameworkPathTestFixture + : public AllocatorsTestFixture + { + public: + void SetUp() override + { + UnitTest::AllocatorsTestFixture::SetUp(); + + m_parentPathAbs = AZStd::string(AZStd::string("parent") + AZ_TRAIT_OS_PATH_SEPARATOR + "path"); + m_childPathRel = AZStd::string(AZStd::string("child") + AZ_TRAIT_OS_PATH_SEPARATOR + "path"); + m_childPathAbs = AZStd::string(m_parentPathAbs + AZ_TRAIT_OS_PATH_SEPARATOR + m_childPathRel); + + m_pathComponentA = AZStd::string("DirA"); + m_pathComponentB = AZStd::string("DirB"); + m_pathComponentC = AZStd::string("DirC"); + + m_posixPath = m_pathComponentA + AZ::IO::PosixPathSeparator + m_pathComponentB + AZ::IO::PosixPathSeparator + m_pathComponentC; + + m_windowsPath = + m_pathComponentA + AZ::IO::WindowsPathSeparator + m_pathComponentB + AZ::IO::WindowsPathSeparator + m_pathComponentC; + + m_mixedPath = + m_pathComponentA + AZ::IO::WindowsPathSeparator + m_pathComponentB + AZ::IO::PosixPathSeparator + m_pathComponentC; + + m_referredPath = + m_pathComponentA + AZ_TRAIT_OS_PATH_SEPARATOR + m_pathComponentB + AZ_TRAIT_OS_PATH_SEPARATOR + m_pathComponentC; + } + + void TearDown() override + { + UnitTest::AllocatorsTestFixture::TearDown(); + } + + AZStd::string m_parentPathAbs; + AZStd::string m_childPathRel; + AZStd::string m_childPathAbs; + + AZStd::string m_pathComponentA; + AZStd::string m_pathComponentB; + AZStd::string m_pathComponentC; + + AZ::IO::Path m_posixPath; + AZ::IO::Path m_windowsPath; + AZ::IO::Path m_mixedPath; + AZ::IO::Path m_referredPath; + }; + + TEST_F(FrameworkPathTestFixture, DefaultConstructor_HasEmptyAbsAndRelPaths) + { + // Given an empty framework path + TestImpact::FrameworkPath path; + + // Expect the absolute path to be empty + EXPECT_TRUE(path.Absolute().empty()); + + // Expect the relative path to be empty + EXPECT_TRUE(path.Relative().empty()); + } + + TEST_F(FrameworkPathTestFixture, OrphanConstructor_HasAbsAndEmptyRelPaths) + { + // Given an orhpan framework path + TestImpact::FrameworkPath path(m_parentPathAbs); + + // Expect the absolute path to be equal to the specified path + EXPECT_EQ(path.Absolute().String(), m_parentPathAbs); + + // Expect the relative path to be current directory symbol + EXPECT_STREQ(path.Relative().c_str(), "."); + } + + TEST_F(FrameworkPathTestFixture, ParentConstructor_HasAbsAndRelPaths) + { + // Given a child framework path + TestImpact::FrameworkPath path(m_childPathAbs, TestImpact::FrameworkPath(m_parentPathAbs)); + + // Expect the absolute path to be equal to the concatenation of the parent and child path + EXPECT_EQ(path.Absolute().String(), m_childPathAbs); + + // Expect the relative path to equal to the specified path + EXPECT_EQ(path.Relative().String(), m_childPathRel); + } + + TEST_F(FrameworkPathTestFixture, PosixSeperators_HasUniformPreferredSeperators) + { + // Given an orhpan framework path with Posix separators + TestImpact::FrameworkPath path(m_posixPath); + + // Expect the absolute path to be equal to the specified path with preferred separators + EXPECT_EQ(path.Absolute(), m_referredPath); + + // Expect the relative path to be current directory symbol + EXPECT_STREQ(path.Relative().c_str(), "."); + } + + TEST_F(FrameworkPathTestFixture, WindowsSeperators_HasUniformPreferredSeperators) + { + // Given an orhpan framework path with Windows separators + TestImpact::FrameworkPath path(m_windowsPath); + + // Expect the absolute path to be equal to the specified path with preferred separators + EXPECT_EQ(path.Absolute(), m_referredPath); + + // Expect the relative path to be current directory symbol + EXPECT_STREQ(path.Relative().c_str(), "."); + } + + TEST_F(FrameworkPathTestFixture, MixedSeperators_HasUniformPreferredSeperators) + { + // Given an orhpan framework path with mixed separators + TestImpact::FrameworkPath path(m_windowsPath); + + // Expect the absolute path to be equal to the specified path with preferred separators + EXPECT_EQ(path.Absolute(), m_referredPath); + + // Expect the relative path to be current directory symbol + EXPECT_STREQ(path.Relative().c_str(), "."); + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestJobRunnerCommon.h b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestJobRunnerCommon.h new file mode 100644 index 0000000000..9d5ddd6c8c --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestJobRunnerCommon.h @@ -0,0 +1,159 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include +#include + +#include +#include +#include + +template +constexpr bool IsFlagSet(Flags flags, Flags flag) +{ + return TestImpact::Bitwise::IsFlagSet(flags, flag); +} + +namespace UnitTest +{ + // Named constants for array of targets lookup + enum + { + TestTargetA, + TestTargetB, + TestTargetC, + TestTargetD + }; + + // Named constants for max concurrency values + enum + { + OneConcurrentProcess = 1, + FourConcurrentProcesses = 4 + }; + + // Validates that the specified job was executed and returned successfully + template + void ValidateJobExecutedSuccessfully(const Job& job) + { + EXPECT_EQ(job.GetResult(), TestImpact::JobResult::ExecutedWithSuccess); + EXPECT_TRUE(job.GetDuration() > AZStd::chrono::milliseconds(0)); + EXPECT_TRUE(job.GetReturnCode().has_value()); + EXPECT_EQ(job.GetReturnCode(), 0); + EXPECT_TRUE(job.GetPayload().has_value()); + } + + // Validates that the specified job has not been executed + template + void ValidateJobNotExecuted(const Job& job) + { + EXPECT_EQ(job.GetResult(), TestImpact::JobResult::NotExecuted); + EXPECT_EQ(job.GetStartTime(), AZStd::chrono::high_resolution_clock::time_point()); + EXPECT_EQ(job.GetEndTime(), AZStd::chrono::high_resolution_clock::time_point()); + EXPECT_EQ(job.GetDuration(), AZStd::chrono::milliseconds(0)); + EXPECT_FALSE(job.GetReturnCode().has_value()); + EXPECT_FALSE(job.GetPayload().has_value()); + } + + // Validates that the specified job failed to execute + template + void ValidateJobFailedToExecute(const Job& job) + { + EXPECT_EQ(job.GetResult(), TestImpact::JobResult::FailedToExecute); + EXPECT_EQ(job.GetStartTime(), AZStd::chrono::high_resolution_clock::time_point()); + EXPECT_EQ(job.GetEndTime(), AZStd::chrono::high_resolution_clock::time_point()); + EXPECT_EQ(job.GetDuration(), AZStd::chrono::milliseconds(0)); + EXPECT_FALSE(job.GetReturnCode().has_value()); + EXPECT_FALSE(job.GetPayload().has_value()); + } + + // Validates that the specified job executed but returned with error + template + void ValidateJobExecutedWithFailure(const Job& job) + { + EXPECT_EQ(job.GetResult(), TestImpact::JobResult::ExecutedWithFailure); + EXPECT_TRUE(job.GetDuration() > AZStd::chrono::milliseconds(0)); + EXPECT_TRUE(job.GetReturnCode().has_value()); + EXPECT_GT(job.GetReturnCode().value(), 0); + EXPECT_FALSE(job.GetPayload().has_value()); + } + + // Validates that the specified job was executed but was terminated by the job runner + template + void ValidateJobTimeout(const Job& job) + { + EXPECT_EQ(job.GetResult(), TestImpact::JobResult::Terminated); + EXPECT_TRUE(job.GetDuration() > AZStd::chrono::milliseconds(0)); + EXPECT_TRUE(job.GetReturnCode().has_value()); + EXPECT_EQ(job.GetReturnCode().value(), TestImpact::ProcessTimeoutErrorCode); + EXPECT_FALSE(job.GetPayload().has_value()); + } + + // Validates that the specified job executed but returned with error + template + void ValidateJobExecutedWithFailedTests(const Job& job) + { + EXPECT_EQ(job.GetResult(), TestImpact::JobResult::ExecutedWithFailure); + EXPECT_TRUE(job.GetDuration() > AZStd::chrono::milliseconds(0)); + EXPECT_TRUE(job.GetReturnCode().has_value()); + EXPECT_GT(job.GetReturnCode().value(), 0); + EXPECT_TRUE(job.GetPayload().has_value()); + } + + // Validates whether a test run completed (passed/failed) + template + void ValidateTestRunCompleted(const Job& job, TestImpact::TestRunResult result) + { + if (result == TestImpact::TestRunResult::Passed) + { + ValidateJobExecutedSuccessfully(job); + } + else + { + ValidateJobExecutedWithFailedTests(job); + } + } + + // Validates that the specified test run matches the expected output + inline void ValidateTestTargetRun(const TestImpact::TestRun& actualResult, const TestImpact::TestRun& expectedResult) + { + EXPECT_TRUE(CheckTestRunsAreEqualIgnoreDurations(actualResult, expectedResult)); + EXPECT_EQ(actualResult.GetNumTestSuites(), CalculateNumTestSuites(expectedResult.GetTestSuites())); + EXPECT_EQ(actualResult.GetNumTests(), CalculateNumTests(expectedResult.GetTestSuites())); + EXPECT_EQ(actualResult.GetNumEnabledTests(), CalculateNumEnabledTests(expectedResult.GetTestSuites())); + EXPECT_EQ(actualResult.GetNumDisabledTests(), CalculateNumDisabledTests(expectedResult.GetTestSuites())); + EXPECT_GT(actualResult.GetDuration(), AZStd::chrono::milliseconds{0}); + EXPECT_EQ(actualResult.GetNumPasses(), CalculateNumPassedTests(expectedResult.GetTestSuites())); + EXPECT_EQ(actualResult.GetNumFailures(), CalculateNumFailedTests(expectedResult.GetTestSuites())); + EXPECT_EQ(actualResult.GetNumRuns(), CalculateNumRunTests(expectedResult.GetTestSuites())); + EXPECT_EQ(actualResult.GetNumNotRuns(), CalculateNumNotRunTests(expectedResult.GetTestSuites())); + } + + // Delete any existing data in the test run folder as not to pollute tests with data from previous test runs + // Note: the file IO operations of this fixture means it cannot be sharded by the test sharder due to file race conditions + inline void DeleteFiles(const AZStd::string& path, const AZStd::string& pattern) + { + AZ::IO::SystemFile::FindFiles(AZStd::string::format("%s/%s", path.c_str(), pattern.c_str()).c_str(), [&path](const char* file, bool isFile) + { + if (isFile) + { + AZ::IO::SystemFile::Delete(AZStd::string::format("%s/%s", path.c_str(), file).c_str()); + } + + return true; + }); + }; +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestMain.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestMain.cpp new file mode 100644 index 0000000000..632e84ad9f --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestMain.cpp @@ -0,0 +1,33 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include + +class TestImpactTestEnvironment : public AZ::Test::ITestEnvironment +{ +protected: + void SetupEnvironment() override + { + AZ::AllocatorInstance::Create(); + AZ::AllocatorInstance::Create(); + } + + void TeardownEnvironment() override + { + AZ::AllocatorInstance::Destroy(); + AZ::AllocatorInstance::Destroy(); + } +}; + +AZ_UNIT_TEST_HOOK(new TestImpactTestEnvironment); diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestUtils.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestUtils.cpp new file mode 100644 index 0000000000..90f384de85 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestUtils.cpp @@ -0,0 +1,1843 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include + +#include +#include + +namespace UnitTest +{ + void WriteTextToFile(const AZStd::string& text, const AZ::IO::Path& path) + { + const AZStd::vector textBytes(text.begin(), text.end()); + AZ::IO::SystemFile textFile; + AZ_TestImpact_Eval( + textFile.Open( + path.c_str(), + AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY), + TestImpact::Exception, "Couldn't open cache file for writing"); + const auto bytesWritten = textFile.Write(textBytes.data(), textBytes.size()); + AZ_TestImpact_Eval(bytesWritten == textBytes.size(), TestImpact::Exception, "Couldn't write text file data"); + } + + AZStd::string ConstructTestProcessArgs(TestImpact::ProcessId pid, AZStd::chrono::milliseconds sleepTime) + { + return AZStd::string::format("--id %i --sleep %u", pid, sleepTime.count()); + } + + AZStd::string ConstructTestProcessArgsLargeText(TestImpact::ProcessId pid, AZStd::chrono::milliseconds sleepTime) + { + return ConstructTestProcessArgs(pid, sleepTime) + " large"; + } + + AZStd::string KnownTestProcessOutputString(TestImpact::ProcessId pid) + { + return AZStd::string("TestProcessMainStdOut" + AZStd::to_string(pid)); + } + + AZStd::string KnownTestProcessErrorString(TestImpact::ProcessId pid) + { + return AZStd::string("TestProcessMainStdErr" + AZStd::to_string(pid)); + } + + AZStd::string GenerateSuiteOrFixtureName(const AZStd::string& name) + { + return name; + } + + AZStd::string GenerateTypedFixtureName(const AZStd::string& name, size_t typeNum) + { + return AZStd::string::format("%s/%u", name.c_str(), typeNum); + } + + AZStd::string GenerateParameterizedFixtureName(const AZStd::string& name, const AZStd::optional& prefix) + { + if (prefix.has_value()) + { + return AZStd::string::format("%s/%s", prefix->c_str(), name.c_str()); + } + + return AZStd::string::format("%s", name.c_str()); + } + + AZStd::string GenerateParameterizedTestName(const AZStd::string& name, size_t testNum) + { + return AZStd::string::format("%s/%u", name.c_str(), testNum); + } + + AZStd::string StringVectorToJSONElements(const AZStd::vector strings) + { + AZStd::string output; + + for (size_t i = 0, last = strings.size() - 1; i < strings.size(); i++) + { + output += AZStd::string::format("\"%s\"", strings[i].c_str()); + + if (i != last) + { + output += ","; + } + + output += "\n"; + } + + return output; + } + + AZStd::string GenerateBuildTargetDescriptorString( + const AZStd::string& name, + const AZStd::string& outputName, + const AZStd::string& path, + const AZStd::vector& staticSources, + const AZStd::vector& autogenInputs, + const AZStd::vector& autogenOutputs) + { + constexpr const char* const targetTemplate = + "{\n" + " \"sources\": {\n" + " \"input\": [\n" + "%s\n" + " ],\n" + " \"output\": [\n" + "%s\n" + " ],\n" + " \"static\": [\n" + "%s\n" + " ]\n" + " },\n" + " \"target\": {\n" + " \"name\": \"%s\",\n" + " \"output_name\": \"%s\",\n" + " \"path\": \"%s\"\n" + " }\n" + "}\n" + "\n"; + + AZStd::string output = AZStd::string::format(targetTemplate, + StringVectorToJSONElements(autogenInputs).c_str(), + StringVectorToJSONElements(autogenOutputs).c_str(), + StringVectorToJSONElements(staticSources).c_str(), + name.c_str(), + outputName.c_str(), + path.c_str()); + + return output; + } + + TestImpact::BuildTargetDescriptor GenerateBuildTargetDescriptor( + const AZStd::string& name, + const AZStd::string& outputName, + const AZStd::string& path, + const AZStd::vector& staticSources, + const TestImpact::AutogenSources& autogenSources) + { + TestImpact::BuildTargetDescriptor targetDescriptor; + targetDescriptor.m_buildMetaData = {name, outputName, path}; + targetDescriptor.m_sources = {staticSources, autogenSources}; + return targetDescriptor; + } + + TestImpact::TestEnumerationSuite GenerateParamterizedSuite( + const AZStd::pair& fixture, + const AZStd::optional& permutation, + const AZStd::vector> tests, + size_t permutationCount) + { + TestImpact::TestEnumerationSuite suite = + TestImpact::TestEnumerationSuite{GenerateParameterizedFixtureName(fixture.first, permutation), fixture.second, {}}; + + for (const auto& test : tests) + { + for (auto i = 0; i < permutationCount; i++) + { + suite.m_tests.push_back(TestImpact::TestEnumerationCase{GenerateParameterizedTestName(test.first, i), test.second}); + } + } + + return suite; + } + + void GenerateTypedSuite( + const AZStd::pair& fixture, + const AZStd::vector> tests, + size_t permutationCount, + AZStd::vector& parentSuiteList) + { + for (auto i = 0; i < permutationCount; i++) + { + parentSuiteList.push_back(TestImpact::TestEnumerationSuite{GenerateTypedFixtureName(fixture.first, i), fixture.second, {}}); + + for (const auto& test : tests) + { + parentSuiteList.back().m_tests.push_back(TestImpact::TestEnumerationCase{test.first, test.second}); + } + } + } + + size_t CalculateNumPassedTests(const AZStd::vector& suites) + { + size_t numPassedTests = 0; + for (const auto& suite : suites) + { + numPassedTests += AZStd::count_if(suite.m_tests.begin(), suite.m_tests.end(), [](const auto& test) { + return test.m_result.has_value() && test.m_result.value() == TestImpact::TestRunResult::Passed; + }); + } + + return numPassedTests; + } + + size_t CalculateNumFailedTests(const AZStd::vector& suites) + { + size_t numFailedTests = 0; + for (const auto& suite : suites) + { + for (const auto& test : suite.m_tests) + { + if (test.m_result.has_value() && test.m_result.value() == TestImpact::TestRunResult::Failed) + { + numFailedTests++; + } + } + } + + return numFailedTests; + } + + size_t CalculateNumRunTests(const AZStd::vector& suites) + { + size_t numRunTests = 0; + for (const auto& suite : suites) + { + for (const auto& test : suite.m_tests) + { + if (test.m_status == TestImpact::TestRunStatus::Run) + { + numRunTests++; + } + } + } + + return numRunTests; + } + + size_t CalculateNumNotRunTests(const AZStd::vector& suites) + { + size_t numNotRunTests = 0; + for (const auto& suite : suites) + { + for (const auto& test : suite.m_tests) + { + if (test.m_status == TestImpact::TestRunStatus::NotRun) + { + numNotRunTests++; + } + } + } + + return numNotRunTests; + } + + AZStd::vector GetTestTargetATestEnumerationSuites() + { + AZStd::vector suites; + + suites.push_back(TestImpact::TestEnumerationSuite{GenerateSuiteOrFixtureName("TestCase"), true, {}}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test1_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test2_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test3_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test4_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test5_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test6_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test7_WillFail", true}); + + suites.push_back(TestImpact::TestEnumerationSuite{GenerateSuiteOrFixtureName("TestFixture"), true, {}}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test1_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test2_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test3_WillPass", true}); + + return suites; + } + + AZStd::vector GetTestTargetBTestEnumerationSuites() + { + AZStd::vector suites; + + suites.push_back(TestImpact::TestEnumerationSuite{GenerateSuiteOrFixtureName("TestCase"), true, {}}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test1_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test2_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test3_WillPass", true}); + + suites.push_back(TestImpact::TestEnumerationSuite{GenerateSuiteOrFixtureName("TestFixture"), true, {}}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test1_WillPass", true}); + + const size_t numParams = 27; + suites.push_back(GenerateParamterizedSuite( + {"TestFixtureWithParams", true}, "PermutationA", {{"Test1_WillPass", true}, {"Test2_WillPass", true}}, numParams)); + suites.push_back(GenerateParamterizedSuite( + {"TestFixtureWithParams", true}, AZStd::nullopt, {{"Test1_WillPass", true}, {"Test2_WillPass", true}}, numParams)); + + return suites; + } + + AZStd::vector GetTestTargetCTestEnumerationSuites() + { + AZStd::vector suites; + + suites.push_back(TestImpact::TestEnumerationSuite{GenerateSuiteOrFixtureName("TestFixture"), true, {}}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test1_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test2_WillPass", true}); + + const size_t numTypes = 4; + for (auto i = 0; i < numTypes; i++) + { + suites.push_back(TestImpact::TestEnumerationSuite{GenerateTypedFixtureName("TestFixtureWithTypes", i), true, {}}); + for (auto j = 1; j < numTypes + 1; j++) + { + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{AZStd::string::format("Test%u_WillPass", j), true}); + } + } + + return suites; + } + + AZStd::vector GetTestTargetDTestEnumerationSuites() + { + AZStd::vector suites; + + suites.push_back(TestImpact::TestEnumerationSuite{GenerateSuiteOrFixtureName("TestCase"), true, {}}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test1_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"DISABLED_Test2_WillPass", false}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test3_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test4_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test5_WillPass", true}); + + suites.push_back(TestImpact::TestEnumerationSuite{GenerateSuiteOrFixtureName("TestFixture1"), true, {}}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test1_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test2_WillPass", true}); + + suites.push_back(TestImpact::TestEnumerationSuite{GenerateSuiteOrFixtureName("DISABLED_TestFixture2"), false, {}}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test1_WillPass", true}); + suites.back().m_tests.push_back(TestImpact::TestEnumerationCase{"Test2_WillPass", true}); + + const size_t numTypes = 4; + GenerateTypedSuite( + {"TestFixtureWithTypes1", true}, {{"Test1_WillPass", true}, {"DISABLED_Test2_WillPass", false}, {"Test3_WillPass", true}}, + numTypes, suites); + + GenerateTypedSuite( + {"DISABLED_TestFixtureWithTypes2", false}, + {{"Test1_WillPass", true}, {"DISABLED_Test2_WillPass", false}, {"Test3_WillPass", true}}, numTypes, suites); + + const size_t numParams = 27; + suites.push_back(GenerateParamterizedSuite( + {"TestFixtureWithParams1", true}, "PermutationA", {{"Test1_WillPass", true}, {"DISABLED_Test2_WillPass", false}}, numParams)); + suites.push_back(GenerateParamterizedSuite( + {"TestFixtureWithParams1", true}, AZStd::nullopt, {{"Test1_WillPass", true}, {"DISABLED_Test2_WillPass", false}}, numParams)); + suites.push_back(GenerateParamterizedSuite( + {"DISABLED_TestFixtureWithParams2", false}, "PermutationA", {{"Test1_WillPass", true}, {"DISABLED_Test2_WillPass", false}}, + numParams)); + suites.push_back(GenerateParamterizedSuite( + {"DISABLED_TestFixtureWithParams2", false}, AZStd::nullopt, {{"Test1_WillPass", true}, {"DISABLED_Test2_WillPass", false}}, + numParams)); + + return suites; + } + + TestImpact::TestRunCase TestRunCaseFromTestEnumerationCase(const TestImpact::TestEnumerationCase& enumCase) + { + TestImpact::TestRunCase runCase; + runCase.m_name = enumCase.m_name; + runCase.m_enabled = enumCase.m_enabled; + return runCase; + } + + TestImpact::TestRunSuite TestRunSuiteFromTestEnumerationSuite(const TestImpact::TestEnumerationSuite& enumSuite) + { + TestImpact::TestRunSuite runSuite; + runSuite.m_name = enumSuite.m_name; + runSuite.m_enabled = enumSuite.m_enabled; + + for (const auto& enumCase : enumSuite.m_tests) + { + runSuite.m_tests.push_back(TestRunCaseFromTestEnumerationCase(enumCase)); + } + + return runSuite; + } + + AZStd::vector TestRunSuitesFromTestEnumerationSuites( + const AZStd::vector& enumSuites) + { + AZStd::vector runSuites; + runSuites.reserve(enumSuites.size()); + + for (const auto& enumSuite : enumSuites) + { + runSuites.push_back(TestRunSuiteFromTestEnumerationSuite(enumSuite)); + } + + return runSuites; + } + + void SetTestRunSuiteData(TestImpact::TestRunSuite& testSuite, AZStd::chrono::milliseconds duration) + { + testSuite.m_duration = duration; + } + + void SetTestRunCaseData( + TestImpact::TestRunCase& testCase, AZStd::chrono::milliseconds duration, TestImpact::TestRunStatus status, + AZStd::optional result = AZStd::nullopt) + { + testCase.m_duration = duration; + testCase.m_status = status; + testCase.m_result = result; + } + + AZStd::vector GetTestTargetATestRunSuites() + { + AZStd::vector suites(TestRunSuitesFromTestEnumerationSuites(GetTestTargetATestEnumerationSuites())); + + enum + { + TestCaseIndex = 0, + TestFixtureIndex + }; + + { + auto& suite = suites[TestCaseIndex]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{3}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[3], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[4], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[5], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[6], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Failed); + } + { + auto& suite = suites[TestFixtureIndex]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{38}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{4}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + + return suites; + } + + AZStd::vector GetTestTargetBTestRunSuites() + { + AZStd::vector suites(TestRunSuitesFromTestEnumerationSuites(GetTestTargetBTestEnumerationSuites())); + + enum + { + TestCaseIndex = 0, + TestFixtureIndex, + PermutationATestFixtureWithParamsIndex, + TestFixtureWithParamsIndex + }; + + { + auto& suite = suites[TestCaseIndex]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{202}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{3}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[TestFixtureIndex]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{62}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{5}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[PermutationATestFixtureWithParamsIndex]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{3203}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[3], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[4], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[5], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[6], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[7], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[8], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[9], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[10], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[11], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[12], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[13], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[14], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[15], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[16], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[17], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[18], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[19], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[20], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[21], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[22], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[23], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[24], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[25], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[26], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[27], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[28], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[29], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[30], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[31], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[32], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[33], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[34], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[35], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[36], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[37], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[38], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[39], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[40], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[41], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[42], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[43], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[44], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[45], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[46], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[47], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[48], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[49], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[50], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[51], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[52], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[53], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[TestFixtureWithParamsIndex]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{3360}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[3], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[4], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[5], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[6], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[7], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[8], AZStd::chrono::milliseconds{2}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[9], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[10], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[11], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[12], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[13], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[14], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[15], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[16], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[17], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[18], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[19], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[20], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[21], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[22], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[23], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[24], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[25], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[26], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[27], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[28], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[29], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[30], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[31], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[32], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[33], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[34], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[35], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[36], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[37], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[38], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[39], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[40], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[41], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[42], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[43], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[44], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[45], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[46], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[47], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[48], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[49], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[50], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[51], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[52], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[53], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + + return suites; + } + + AZStd::vector GetTestTargetCTestRunSuites() + { + AZStd::vector suites(TestRunSuitesFromTestEnumerationSuites(GetTestTargetCTestEnumerationSuites())); + + enum + { + TestCaseIndex = 0, + TestFixtureWithTypes0Index, + TestFixtureWithTypes1Index, + TestFixtureWithTypes2Index, + TestFixtureWithTypes3Index + }; + + { + auto& suite = suites[TestCaseIndex]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{125}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{4}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[TestFixtureWithTypes0Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{210}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[3], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[TestFixtureWithTypes1Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{208}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[3], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[TestFixtureWithTypes2Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{199}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[3], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[TestFixtureWithTypes3Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{49}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[3], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + + return suites; + } + + AZStd::vector GetTestTargetDTestRunSuites() + { + AZStd::vector suites(TestRunSuitesFromTestEnumerationSuites(GetTestTargetDTestEnumerationSuites())); + + enum + { + TestCaseIndex = 0, + TestFixture1Index, + DISABLEDTestFixture2Index, + TestFixtureWithTypes10Index, + TestFixtureWithTypes11Index, + TestFixtureWithTypes12Index, + TestFixtureWithTypes13Index, + DISABLEDTestFixtureWithTypes20Index, + DISABLEDTestFixtureWithTypes21Index, + DISABLEDTestFixtureWithTypes22Index, + DISABLEDTestFixtureWithTypes23Index, + PermutationATestFixtureWithParams1Index, + TestFixtureWithParams1Index, + PermutationADISABLED_TestFixtureWithParams2Index, + DISABLED_TestFixtureWithParams2Index, + }; + + { + auto& suite = suites[TestCaseIndex]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{3}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData(suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[3], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[4], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[TestFixture1Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{4}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{2}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[DISABLEDTestFixture2Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{0}); + SetTestRunCaseData(suite.m_tests[0], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + } + { + auto& suite = suites[TestFixtureWithTypes10Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{1}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData(suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[TestFixtureWithTypes11Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{3}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData(suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[TestFixtureWithTypes12Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{0}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData(suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[TestFixtureWithTypes13Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{1}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData(suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + } + { + auto& suite = suites[DISABLEDTestFixtureWithTypes20Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{0}); + SetTestRunCaseData(suite.m_tests[0], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + } + { + auto& suite = suites[DISABLEDTestFixtureWithTypes21Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{0}); + SetTestRunCaseData(suite.m_tests[0], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + } + { + auto& suite = suites[DISABLEDTestFixtureWithTypes22Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{0}); + SetTestRunCaseData(suite.m_tests[0], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + } + { + auto& suite = suites[DISABLEDTestFixtureWithTypes23Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{0}); + SetTestRunCaseData(suite.m_tests[0], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + } + { + auto& suite = suites[PermutationATestFixtureWithParams1Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{173}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[3], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[4], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[5], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[6], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[7], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[8], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[9], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[10], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[11], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[12], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[13], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[14], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[15], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[16], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[17], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[18], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[19], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[20], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[21], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[22], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[23], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[24], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[25], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[26], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData(suite.m_tests[27], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[28], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[29], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[30], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[31], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[32], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[33], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[34], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[35], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[36], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[37], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[38], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[39], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[40], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[41], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[42], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[43], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[44], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[45], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[46], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[47], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[48], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[49], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[50], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[51], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[52], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[53], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + } + { + auto& suite = suites[TestFixtureWithParams1Index]; + SetTestRunSuiteData(suite, AZStd::chrono::milliseconds{102}); + SetTestRunCaseData( + suite.m_tests[0], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[1], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[2], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[3], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[4], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[5], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[6], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[7], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[8], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[9], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[10], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[11], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[12], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[13], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[14], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[15], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[16], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[17], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[18], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[19], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[20], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[21], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[22], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[23], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[24], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[25], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData( + suite.m_tests[26], AZStd::chrono::milliseconds{1}, TestImpact::TestRunStatus::Run, TestImpact::TestRunResult::Passed); + SetTestRunCaseData(suite.m_tests[27], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[28], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[29], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[30], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[31], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[32], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[33], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[34], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[35], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[36], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[37], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[38], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[39], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[40], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[41], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[42], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[43], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[44], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[45], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[46], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[47], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[48], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[49], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[50], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[51], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[52], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + SetTestRunCaseData(suite.m_tests[53], AZStd::chrono::milliseconds{0}, TestImpact::TestRunStatus::NotRun); + } + // The other two fixtures are all disabled (not run) + + return suites; + } + + AZStd::vector GetTestTargetASourceModuleCoverages() + { + AZStd::vector moduleCoverages; + + TestImpact::ModuleCoverage moduleCoverage; + moduleCoverage.m_path = "C:\\Lumberyard\\windows_vs2019\\bin\\debug\\TestImpact.TestTargetA.Tests.dll"; + + TestImpact::SourceCoverage sourceCoverage; + sourceCoverage.m_path = + "C:\\Lumberyard\\Code\\Tools\\TestImpactFramework\\Runtime\\Code\\Tests\\TestTargetA\\Code\\Tests\\TestImpactTestTargetA.cpp"; + + moduleCoverage.m_sources.push_back(AZStd::move(sourceCoverage)); + moduleCoverages.push_back(AZStd::move(moduleCoverage)); + return moduleCoverages; + } + + AZStd::vector GetTestTargetBSourceModuleCoverages() + { + AZStd::vector moduleCoverages; + + TestImpact::ModuleCoverage moduleCoverage; + moduleCoverage.m_path = "C:\\Lumberyard\\windows_vs2019\\bin\\debug\\TestImpact.TestTargetB.Tests.dll"; + + TestImpact::SourceCoverage sourceCoverage; + sourceCoverage.m_path = + "C:\\Lumberyard\\Code\\Tools\\TestImpactFramework\\Runtime\\Code\\Tests\\TestTargetB\\Code\\Tests\\TestImpactTestTargetB.cpp"; + + moduleCoverage.m_sources.push_back(AZStd::move(sourceCoverage)); + moduleCoverages.push_back(AZStd::move(moduleCoverage)); + return moduleCoverages; + } + + AZStd::vector GetTestTargetCSourceModuleCoverages() + { + AZStd::vector moduleCoverages; + + TestImpact::ModuleCoverage moduleCoverage; + moduleCoverage.m_path = "C:\\Lumberyard\\windows_vs2019\\bin\\debug\\TestImpact.TestTargetC.Tests.dll"; + + TestImpact::SourceCoverage sourceCoverage; + sourceCoverage.m_path = + "C:\\Lumberyard\\Code\\Tools\\TestImpactFramework\\Runtime\\Code\\Tests\\TestTargetC\\Code\\Tests\\TestImpactTestTargetC.cpp"; + + moduleCoverage.m_sources.push_back(AZStd::move(sourceCoverage)); + moduleCoverages.push_back(AZStd::move(moduleCoverage)); + return moduleCoverages; + } + + AZStd::vector GetTestTargetDSourceModuleCoverages() + { + AZStd::vector moduleCoverages; + + TestImpact::ModuleCoverage moduleCoverage; + moduleCoverage.m_path = "C:\\Lumberyard\\windows_vs2019\\bin\\debug\\TestImpact.TestTargetD.Tests.dll"; + + TestImpact::SourceCoverage sourceCoverage; + sourceCoverage.m_path = + "C:\\Lumberyard\\Code\\Tools\\TestImpactFramework\\Runtime\\Code\\Tests\\TestTargetD\\Code\\Tests\\TestImpactTestTargetD.cpp"; + + moduleCoverage.m_sources.push_back(AZStd::move(sourceCoverage)); + moduleCoverages.push_back(AZStd::move(moduleCoverage)); + return moduleCoverages; + } + + AZStd::vector GetTestTargetALineModuleCoverages() + { + AZStd::vector moduleCoverages; + + TestImpact::ModuleCoverage moduleCoverage; + moduleCoverage.m_path = "C:\\Lumberyard\\windows_vs2019\\bin\\debug\\TestImpact.TestTargetA.Tests.dll"; + + TestImpact::SourceCoverage sourceCoverage; + sourceCoverage.m_path = + "C:\\Lumberyard\\Code\\Tools\\TestImpactFramework\\Runtime\\Code\\Tests\\TestTargetA\\Code\\Tests\\TestImpactTestTargetA.cpp"; + sourceCoverage.m_coverage = AZStd::vector(); + + auto& lines = sourceCoverage.m_coverage.value(); + lines.push_back(TestImpact::LineCoverage{22, 1}); + lines.push_back(TestImpact::LineCoverage{23, 1}); + lines.push_back(TestImpact::LineCoverage{24, 1}); + lines.push_back(TestImpact::LineCoverage{25, 1}); + lines.push_back(TestImpact::LineCoverage{27, 1}); + lines.push_back(TestImpact::LineCoverage{28, 1}); + lines.push_back(TestImpact::LineCoverage{29, 1}); + lines.push_back(TestImpact::LineCoverage{30, 1}); + lines.push_back(TestImpact::LineCoverage{32, 1}); + lines.push_back(TestImpact::LineCoverage{33, 1}); + lines.push_back(TestImpact::LineCoverage{34, 1}); + lines.push_back(TestImpact::LineCoverage{35, 1}); + lines.push_back(TestImpact::LineCoverage{37, 1}); + lines.push_back(TestImpact::LineCoverage{38, 1}); + lines.push_back(TestImpact::LineCoverage{39, 1}); + lines.push_back(TestImpact::LineCoverage{40, 1}); + lines.push_back(TestImpact::LineCoverage{42, 1}); + lines.push_back(TestImpact::LineCoverage{43, 1}); + lines.push_back(TestImpact::LineCoverage{44, 1}); + lines.push_back(TestImpact::LineCoverage{45, 1}); + lines.push_back(TestImpact::LineCoverage{47, 1}); + lines.push_back(TestImpact::LineCoverage{48, 1}); + lines.push_back(TestImpact::LineCoverage{49, 1}); + lines.push_back(TestImpact::LineCoverage{50, 1}); + lines.push_back(TestImpact::LineCoverage{52, 1}); + lines.push_back(TestImpact::LineCoverage{53, 1}); + lines.push_back(TestImpact::LineCoverage{54, 1}); + lines.push_back(TestImpact::LineCoverage{55, 1}); + lines.push_back(TestImpact::LineCoverage{57, 1}); + lines.push_back(TestImpact::LineCoverage{58, 1}); + lines.push_back(TestImpact::LineCoverage{59, 1}); + lines.push_back(TestImpact::LineCoverage{60, 1}); + lines.push_back(TestImpact::LineCoverage{62, 1}); + lines.push_back(TestImpact::LineCoverage{63, 1}); + lines.push_back(TestImpact::LineCoverage{64, 1}); + lines.push_back(TestImpact::LineCoverage{65, 1}); + lines.push_back(TestImpact::LineCoverage{67, 1}); + lines.push_back(TestImpact::LineCoverage{68, 1}); + lines.push_back(TestImpact::LineCoverage{69, 1}); + lines.push_back(TestImpact::LineCoverage{70, 1}); + lines.push_back(TestImpact::LineCoverage{73, 1}); + + moduleCoverage.m_sources.push_back(AZStd::move(sourceCoverage)); + moduleCoverages.push_back(AZStd::move(moduleCoverage)); + return moduleCoverages; + } + + AZStd::vector GetTestTargetBLineModuleCoverages() + { + AZStd::vector moduleCoverages; + + TestImpact::ModuleCoverage moduleCoverage; + moduleCoverage.m_path = "C:\\Lumberyard\\windows_vs2019\\bin\\debug\\TestImpact.TestTargetB.Tests.dll"; + + TestImpact::SourceCoverage sourceCoverage; + sourceCoverage.m_path = + "C:\\Lumberyard\\Code\\Tools\\TestImpactFramework\\Runtime\\Code\\Tests\\TestTargetB\\Code\\Tests\\TestImpactTestTargetB.cpp"; + sourceCoverage.m_coverage = AZStd::vector(); + + auto& lines = sourceCoverage.m_coverage.value(); + lines.push_back(TestImpact::LineCoverage{29, 1}); + lines.push_back(TestImpact::LineCoverage{30, 1}); + lines.push_back(TestImpact::LineCoverage{31, 1}); + lines.push_back(TestImpact::LineCoverage{32, 1}); + lines.push_back(TestImpact::LineCoverage{34, 1}); + lines.push_back(TestImpact::LineCoverage{35, 1}); + lines.push_back(TestImpact::LineCoverage{36, 1}); + lines.push_back(TestImpact::LineCoverage{37, 1}); + lines.push_back(TestImpact::LineCoverage{39, 1}); + lines.push_back(TestImpact::LineCoverage{40, 1}); + lines.push_back(TestImpact::LineCoverage{41, 1}); + lines.push_back(TestImpact::LineCoverage{42, 1}); + lines.push_back(TestImpact::LineCoverage{44, 1}); + lines.push_back(TestImpact::LineCoverage{45, 1}); + lines.push_back(TestImpact::LineCoverage{46, 1}); + lines.push_back(TestImpact::LineCoverage{47, 1}); + lines.push_back(TestImpact::LineCoverage{49, 1}); + lines.push_back(TestImpact::LineCoverage{50, 1}); + lines.push_back(TestImpact::LineCoverage{51, 1}); + lines.push_back(TestImpact::LineCoverage{52, 1}); + lines.push_back(TestImpact::LineCoverage{54, 1}); + lines.push_back(TestImpact::LineCoverage{55, 1}); + lines.push_back(TestImpact::LineCoverage{56, 1}); + lines.push_back(TestImpact::LineCoverage{57, 1}); + lines.push_back(TestImpact::LineCoverage{59, 1}); + lines.push_back(TestImpact::LineCoverage{66, 1}); + lines.push_back(TestImpact::LineCoverage{68, 1}); + lines.push_back(TestImpact::LineCoverage{75, 1}); + lines.push_back(TestImpact::LineCoverage{78, 1}); + + moduleCoverage.m_sources.push_back(AZStd::move(sourceCoverage)); + moduleCoverages.push_back(AZStd::move(moduleCoverage)); + return moduleCoverages; + } + + AZStd::vector GetTestTargetCLineModuleCoverages() + { + AZStd::vector moduleCoverages; + + TestImpact::ModuleCoverage moduleCoverage; + moduleCoverage.m_path = "C:\\Lumberyard\\windows_vs2019\\bin\\debug\\TestImpact.TestTargetC.Tests.dll"; + + TestImpact::SourceCoverage sourceCoverage; + sourceCoverage.m_path = + "C:\\Lumberyard\\Code\\Tools\\TestImpactFramework\\Runtime\\Code\\Tests\\TestTargetC\\Code\\Tests\\TestImpactTestTargetC.cpp"; + sourceCoverage.m_coverage = AZStd::vector(); + + auto& lines = sourceCoverage.m_coverage.value(); + lines.push_back(TestImpact::LineCoverage{32, 1}); + lines.push_back(TestImpact::LineCoverage{33, 1}); + lines.push_back(TestImpact::LineCoverage{34, 1}); + lines.push_back(TestImpact::LineCoverage{35, 1}); + lines.push_back(TestImpact::LineCoverage{37, 1}); + lines.push_back(TestImpact::LineCoverage{38, 1}); + lines.push_back(TestImpact::LineCoverage{39, 1}); + lines.push_back(TestImpact::LineCoverage{40, 1}); + lines.push_back(TestImpact::LineCoverage{42, 1}); + lines.push_back(TestImpact::LineCoverage{43, 1}); + lines.push_back(TestImpact::LineCoverage{44, 1}); + lines.push_back(TestImpact::LineCoverage{45, 1}); + lines.push_back(TestImpact::LineCoverage{47, 1}); + lines.push_back(TestImpact::LineCoverage{48, 1}); + lines.push_back(TestImpact::LineCoverage{49, 1}); + lines.push_back(TestImpact::LineCoverage{50, 1}); + lines.push_back(TestImpact::LineCoverage{52, 1}); + lines.push_back(TestImpact::LineCoverage{53, 1}); + lines.push_back(TestImpact::LineCoverage{54, 1}); + lines.push_back(TestImpact::LineCoverage{55, 1}); + lines.push_back(TestImpact::LineCoverage{57, 1}); + lines.push_back(TestImpact::LineCoverage{58, 1}); + lines.push_back(TestImpact::LineCoverage{59, 1}); + lines.push_back(TestImpact::LineCoverage{60, 1}); + lines.push_back(TestImpact::LineCoverage{63, 1}); + + moduleCoverage.m_sources.push_back(AZStd::move(sourceCoverage)); + moduleCoverages.push_back(AZStd::move(moduleCoverage)); + return moduleCoverages; + } + + AZStd::vector GetTestTargetDLineModuleCoverages() + { + AZStd::vector moduleCoverages; + + TestImpact::ModuleCoverage moduleCoverage; + moduleCoverage.m_path = "C:\\Lumberyard\\windows_vs2019\\bin\\debug\\TestImpact.TestTargetD.Tests.dll"; + + TestImpact::SourceCoverage sourceCoverage; + sourceCoverage.m_path = + "C:\\Lumberyard\\Code\\Tools\\TestImpactFramework\\Runtime\\Code\\Tests\\TestTargetD\\Code\\Tests\\TestImpactTestTargetD.cpp"; + sourceCoverage.m_coverage = AZStd::vector(); + + auto& lines = sourceCoverage.m_coverage.value(); + lines.push_back(TestImpact::LineCoverage{56, 1}); + lines.push_back(TestImpact::LineCoverage{57, 1}); + lines.push_back(TestImpact::LineCoverage{58, 1}); + lines.push_back(TestImpact::LineCoverage{59, 1}); + lines.push_back(TestImpact::LineCoverage{61, 1}); + lines.push_back(TestImpact::LineCoverage{62, 0}); + lines.push_back(TestImpact::LineCoverage{63, 0}); + lines.push_back(TestImpact::LineCoverage{64, 0}); + lines.push_back(TestImpact::LineCoverage{66, 1}); + lines.push_back(TestImpact::LineCoverage{67, 1}); + lines.push_back(TestImpact::LineCoverage{68, 1}); + lines.push_back(TestImpact::LineCoverage{69, 1}); + lines.push_back(TestImpact::LineCoverage{71, 1}); + lines.push_back(TestImpact::LineCoverage{72, 1}); + lines.push_back(TestImpact::LineCoverage{73, 1}); + lines.push_back(TestImpact::LineCoverage{74, 1}); + lines.push_back(TestImpact::LineCoverage{76, 1}); + lines.push_back(TestImpact::LineCoverage{77, 1}); + lines.push_back(TestImpact::LineCoverage{78, 1}); + lines.push_back(TestImpact::LineCoverage{79, 1}); + lines.push_back(TestImpact::LineCoverage{81, 1}); + lines.push_back(TestImpact::LineCoverage{82, 1}); + lines.push_back(TestImpact::LineCoverage{83, 1}); + lines.push_back(TestImpact::LineCoverage{84, 1}); + lines.push_back(TestImpact::LineCoverage{86, 1}); + lines.push_back(TestImpact::LineCoverage{87, 1}); + lines.push_back(TestImpact::LineCoverage{88, 1}); + lines.push_back(TestImpact::LineCoverage{89, 1}); + lines.push_back(TestImpact::LineCoverage{91, 1}); + lines.push_back(TestImpact::LineCoverage{92, 0}); + lines.push_back(TestImpact::LineCoverage{93, 0}); + lines.push_back(TestImpact::LineCoverage{94, 0}); + lines.push_back(TestImpact::LineCoverage{96, 1}); + lines.push_back(TestImpact::LineCoverage{97, 0}); + lines.push_back(TestImpact::LineCoverage{98, 0}); + lines.push_back(TestImpact::LineCoverage{99, 0}); + lines.push_back(TestImpact::LineCoverage{101, 1}); + lines.push_back(TestImpact::LineCoverage{102, 1}); + lines.push_back(TestImpact::LineCoverage{103, 1}); + lines.push_back(TestImpact::LineCoverage{104, 1}); + lines.push_back(TestImpact::LineCoverage{106, 1}); + lines.push_back(TestImpact::LineCoverage{107, 0}); + lines.push_back(TestImpact::LineCoverage{108, 0}); + lines.push_back(TestImpact::LineCoverage{109, 0}); + lines.push_back(TestImpact::LineCoverage{111, 1}); + lines.push_back(TestImpact::LineCoverage{112, 0}); + lines.push_back(TestImpact::LineCoverage{113, 0}); + lines.push_back(TestImpact::LineCoverage{114, 0}); + lines.push_back(TestImpact::LineCoverage{116, 1}); + lines.push_back(TestImpact::LineCoverage{117, 0}); + lines.push_back(TestImpact::LineCoverage{118, 0}); + lines.push_back(TestImpact::LineCoverage{119, 0}); + lines.push_back(TestImpact::LineCoverage{121, 1}); + lines.push_back(TestImpact::LineCoverage{128, 1}); + lines.push_back(TestImpact::LineCoverage{130, 1}); + lines.push_back(TestImpact::LineCoverage{137, 1}); + lines.push_back(TestImpact::LineCoverage{139, 1}); + lines.push_back(TestImpact::LineCoverage{146, 1}); + lines.push_back(TestImpact::LineCoverage{148, 1}); + lines.push_back(TestImpact::LineCoverage{155, 1}); + lines.push_back(TestImpact::LineCoverage{157, 1}); + lines.push_back(TestImpact::LineCoverage{158, 1}); + lines.push_back(TestImpact::LineCoverage{159, 1}); + lines.push_back(TestImpact::LineCoverage{160, 1}); + lines.push_back(TestImpact::LineCoverage{162, 1}); + lines.push_back(TestImpact::LineCoverage{163, 0}); + lines.push_back(TestImpact::LineCoverage{164, 0}); + lines.push_back(TestImpact::LineCoverage{165, 0}); + lines.push_back(TestImpact::LineCoverage{167, 1}); + lines.push_back(TestImpact::LineCoverage{168, 1}); + lines.push_back(TestImpact::LineCoverage{169, 1}); + lines.push_back(TestImpact::LineCoverage{170, 1}); + lines.push_back(TestImpact::LineCoverage{172, 1}); + lines.push_back(TestImpact::LineCoverage{173, 0}); + lines.push_back(TestImpact::LineCoverage{174, 0}); + lines.push_back(TestImpact::LineCoverage{175, 0}); + lines.push_back(TestImpact::LineCoverage{177, 1}); + lines.push_back(TestImpact::LineCoverage{178, 0}); + lines.push_back(TestImpact::LineCoverage{179, 0}); + lines.push_back(TestImpact::LineCoverage{180, 0}); + lines.push_back(TestImpact::LineCoverage{182, 1}); + lines.push_back(TestImpact::LineCoverage{183, 0}); + lines.push_back(TestImpact::LineCoverage{184, 0}); + lines.push_back(TestImpact::LineCoverage{185, 0}); + lines.push_back(TestImpact::LineCoverage{188, 1}); + + moduleCoverage.m_sources.push_back(AZStd::move(sourceCoverage)); + moduleCoverages.push_back(AZStd::move(moduleCoverage)); + return moduleCoverages; + } + + template + bool CheckTestCasesAreEqual(const TestCase& lhs, const TestCase& rhs) + { + if (lhs.m_enabled != rhs.m_enabled) + { + AZ_Error("CheckTestCasesAreEqual", false, "lhs.m_enabled: %u, rhs.m_enabled: %u", lhs.m_enabled, rhs.m_enabled); + return false; + } + + if (lhs.m_name != rhs.m_name) + { + AZ_Error("CheckTestCasesAreEqual", false, "lhs.m_name: %s, rhs.m_name: %s", lhs.m_name.c_str(), rhs.m_name.c_str()); + return false; + } + + return true; + } + + template + bool CheckTestSuitesAreEqual(const TestSuite& lhs, const TestSuite& rhs) + { + if (lhs.m_enabled != rhs.m_enabled) + { + AZ_Error("CheckTestSuitesAreEqual", false, "lhs.m_enabled: %u, rhs.m_enabled: %u", lhs.m_enabled, rhs.m_enabled); + return false; + } + + if (lhs.m_name != rhs.m_name) + { + AZ_Error("CheckTestSuitesAreEqual", false, "lhs.m_name: %s, rhs.m_name: %s", lhs.m_name.c_str(), rhs.m_name.c_str()); + return false; + } + + return AZStd::equal(lhs.m_tests.begin(), lhs.m_tests.end(), rhs.m_tests.begin(), [](const auto& left, const auto& right) { + return left == right; + }); + } + + template + bool CheckTestSuiteVectorsAreEqual(const AZStd::vector& lhs, const AZStd::vector& rhs) + { + return AZStd::equal(lhs.begin(), lhs.end(), rhs.begin(), [](const TestSuite& left, const TestSuite& right) { + return left == right; + }); + } + + template + bool CheckTestContainersAreEqual(const TestContainer& lhs, const TestContainer& rhs) + { + if (lhs.GetTestSuites().size() != rhs.GetTestSuites().size()) + { + return false; + } + + return AZStd::equal( + lhs.GetTestSuites().begin(), lhs.GetTestSuites().end(), rhs.GetTestSuites().begin(), [](const auto& left, const auto& right) { + return left == right; + }); + } + + bool operator==(const TestImpact::TestEnumerationCase& lhs, const TestImpact::TestEnumerationCase& rhs) + { + return CheckTestCasesAreEqual(lhs, rhs); + } + + bool operator==(const TestImpact::TestRunCase& lhs, const TestImpact::TestRunCase& rhs) + { + if (!CheckTestCasesAreEqual(lhs, rhs)) + { + return false; + } + + if (lhs.m_status != rhs.m_status) + { + AZ_Error( + "TestRunCase ==", false, "lhs.m_status: %u, rhs.m_status: %u", static_cast(lhs.m_status), + static_cast(rhs.m_status)); + return false; + } + + if (lhs.m_duration != rhs.m_duration) + { + AZ_Error("TestRunCase ==", false, "lhs.m_duration: %u, rhs.m_duration: %u", lhs.m_duration.count(), rhs.m_duration.count()); + return false; + } + + if (lhs.m_result != rhs.m_result) + { + if (!lhs.m_result.has_value() && rhs.m_result.has_value()) + { + AZ_Error("TestRunCase ==", false, "lhs.m_result: null, rhs.m_result: %u", static_cast(rhs.m_result.value())); + } + else if (lhs.m_result.has_value() && !rhs.m_result.has_value()) + { + AZ_Error("TestRunCase ==", false, "lhs.m_result: %u, rhs.m_result: null", static_cast(lhs.m_result.value())); + } + else + { + AZ_Error( + "TestRunCase ==", false, "lhs.m_result: %u, rhs.m_result: %u", static_cast(lhs.m_result.value()), + static_cast(rhs.m_result.value())); + } + + return false; + } + + return true; + } + + bool operator==(const TestImpact::TestEnumerationSuite& lhs, const TestImpact::TestEnumerationSuite& rhs) + { + return CheckTestSuitesAreEqual(lhs, rhs); + } + + bool operator==(const TestImpact::TestRunSuite& lhs, const TestImpact::TestRunSuite& rhs) + { + if (!CheckTestSuitesAreEqual(lhs, rhs)) + { + return false; + } + + if (lhs.m_duration != rhs.m_duration) + { + AZ_Error( + "TestEnumerationSuite ==", false, "lhs.m_duration: %u, rhs.m_duration: %u", lhs.m_duration.count(), rhs.m_duration.count()); + return false; + } + + return true; + } + + bool operator==(const AZStd::vector& lhs, const AZStd::vector& rhs) + { + return CheckTestSuiteVectorsAreEqual(lhs, rhs); + } + + bool operator==(const AZStd::vector& lhs, const AZStd::vector& rhs) + { + return CheckTestSuiteVectorsAreEqual(lhs, rhs); + } + + bool operator==(const TestImpact::TestEnumeration& lhs, const TestImpact::TestEnumeration& rhs) + { + return CheckTestContainersAreEqual(lhs, rhs); + } + + bool operator==(const TestImpact::TestRun& lhs, const TestImpact::TestRun& rhs) + { + if (lhs.GetDuration() != rhs.GetDuration()) + { + AZ_Error("TestRun ==", false, "lhs.GetDuration(): %u, rhs.GetDuration(): %u", lhs.GetDuration(), rhs.GetDuration()); + return false; + } + + if (lhs.GetNumDisabledTests() != rhs.GetNumDisabledTests()) + { + AZ_Error( + "TestRun ==", false, "lhs.GetNumDisabledTests(): %u, rhs.GetNumDisabledTests(): %u", lhs.GetNumDisabledTests(), + rhs.GetNumDisabledTests()); + return false; + } + + if (lhs.GetNumEnabledTests() != rhs.GetNumEnabledTests()) + { + AZ_Error( + "TestRun ==", false, "lhs.GetNumEnabledTests(): %u, rhs.GetNumEnabledTests(): %u", lhs.GetNumEnabledTests(), + rhs.GetNumEnabledTests()); + return false; + } + + if (lhs.GetNumFailures() != rhs.GetNumFailures()) + { + AZ_Error("TestRun ==", false, "lhs.GetNumFailures(): %u, rhs.GetNumFailures(): %u", lhs.GetNumFailures(), rhs.GetNumFailures()); + return false; + } + + if (lhs.GetNumNotRuns() != rhs.GetNumNotRuns()) + { + AZ_Error("TestRun ==", false, "lhs.GetNumNotRuns(): %u, rhs.GetNumNotRuns(): %u", lhs.GetNumNotRuns(), rhs.GetNumNotRuns()); + return false; + } + + if (lhs.GetNumPasses() != rhs.GetNumPasses()) + { + AZ_Error("TestRun ==", false, "lhs.GetNumPasses(): %u, rhs.GetNumPasses(): %u", lhs.GetNumPasses(), rhs.GetNumPasses()); + return false; + } + + if (lhs.GetNumRuns() != rhs.GetNumRuns()) + { + AZ_Error("TestRun ==", false, "lhs.GetNumRuns(): %u, rhs.GetNumRuns(): %u", lhs.GetNumRuns(), rhs.GetNumRuns()); + return false; + } + + return CheckTestContainersAreEqual(lhs, rhs); + } + + bool CheckTestRunCaseVectorsAreEqual( + const AZStd::vector& lhs, const AZStd::vector& rhs) + { + return AZStd::equal( + lhs.begin(), lhs.end(), rhs.begin(), [](const TestImpact::TestRunCase& leftCase, const TestImpact::TestRunCase& rightCase) { + if (!CheckTestCasesAreEqual(leftCase, rightCase)) + { + return false; + } + + if (leftCase.m_status != rightCase.m_status) + { + AZ_Error( + "CheckTestRunsAreEqualIgnoreDurations", false, "leftCase.m_status: %u, rightCase.m_status: %u", + static_cast(leftCase.m_status), static_cast(rightCase.m_status)); + return false; + } + + if (leftCase.m_result != rightCase.m_result) + { + if (!leftCase.m_result.has_value() && rightCase.m_result.has_value()) + { + AZ_Error( + "CheckTestRunsAreEqualIgnoreDurations", false, "leftCase.m_result: null, rightCase.m_result: %u", + static_cast(rightCase.m_result.value())); + } + else if (leftCase.m_result.has_value() && !rightCase.m_result.has_value()) + { + AZ_Error( + "CheckTestRunsAreEqualIgnoreDurations", false, "leftCase.m_result: %u, rightCase.m_result: null", + static_cast(leftCase.m_result.value())); + } + else + { + AZ_Error( + "CheckTestRunsAreEqualIgnoreDurations", false, "leftCase.m_result: %u, rightCase.m_result: %u", + static_cast(leftCase.m_result.value()), static_cast(rightCase.m_result.value())); + } + + return false; + } + + return true; + }); + } + + bool CheckTestRunsAreEqualIgnoreDurations(const TestImpact::TestRun& lhs, const TestImpact::TestRun& rhs) + { + return AZStd::equal( + lhs.GetTestSuites().begin(), lhs.GetTestSuites().end(), rhs.GetTestSuites().begin(), + [](const TestImpact::TestRunSuite& leftSuite, const TestImpact::TestRunSuite& rightSuite) { + if (leftSuite.m_enabled != rightSuite.m_enabled) + { + AZ_Error( + "CheckTestRunsAreEqualIgnoreDurations", false, "leftSuite.m_enabled: %u, rightSuite.m_enabled: %u", + leftSuite.m_enabled, rightSuite.m_enabled); + return false; + } + + if (leftSuite.m_name != rightSuite.m_name) + { + AZ_Error( + "CheckTestRunsAreEqualIgnoreDurations", false, "leftSuite.m_name: %s, rightSuite.m_name: %s", + leftSuite.m_name.c_str(), rightSuite.m_name.c_str()); + return false; + } + + return CheckTestRunCaseVectorsAreEqual(leftSuite.m_tests, rightSuite.m_tests); + }); + } + + bool operator==(const TestImpact::BuildMetaData& lhs, const TestImpact::BuildMetaData& rhs) + { + if (lhs.m_name != rhs.m_name) + { + return false; + } + else if (lhs.m_outputName != rhs.m_outputName) + { + return false; + } + else if (lhs.m_path != rhs.m_path) + { + return false; + } + + return true; + } + + bool operator==(const TestImpact::TargetSources& lhs, const TestImpact::TargetSources& rhs) + { + if (lhs.m_staticSources != rhs.m_staticSources) + { + return false; + } + else if (lhs.m_autogenSources.size() != rhs.m_autogenSources.size()) + { + return false; + } + else + { + for (size_t i = 0; i < lhs.m_autogenSources.size(); i++) + { + if (lhs.m_autogenSources[i].m_input != rhs.m_autogenSources[i].m_input) + { + return false; + } + else if (lhs.m_autogenSources[i].m_outputs.size() != rhs.m_autogenSources[i].m_outputs.size()) + { + return false; + } + + for (size_t j = 0; j < lhs.m_autogenSources[i].m_outputs.size(); j++) + { + if (lhs.m_autogenSources[i].m_outputs[j] != rhs.m_autogenSources[i].m_outputs[j]) + { + return false; + } + } + } + } + + return true; + } + + bool operator==(const TestImpact::BuildTargetDescriptor& lhs, const TestImpact::BuildTargetDescriptor& rhs) + { + return lhs.m_buildMetaData == rhs.m_buildMetaData && lhs.m_sources == rhs.m_sources; + } + + bool operator==(const TestImpact::TestTargetMeta& lhs, const TestImpact::TestTargetMeta& rhs) + { + if (lhs.m_suite != rhs.m_suite) + { + return false; + } + else if (lhs.m_launchMethod != rhs.m_launchMethod) + { + return false; + } + + return true; + } + + bool operator==(const TestImpact::ProductionTargetDescriptor& lhs, const TestImpact::ProductionTargetDescriptor& rhs) + { + return lhs.m_buildMetaData == rhs.m_buildMetaData; + } + + bool operator==(const TestImpact::TestTargetDescriptor& lhs, const TestImpact::TestTargetDescriptor& rhs) + { + return lhs.m_buildMetaData == rhs.m_buildMetaData && lhs.m_sources == rhs.m_sources && lhs.m_testMetaData == rhs.m_testMetaData; + } + + bool operator==(const TestImpact::LineCoverage& lhs, const TestImpact::LineCoverage& rhs) + { + if (lhs.m_hitCount != rhs.m_hitCount) + { + AZ_Error("LineCoverage ==", false, "lhs.m_hitCount: %u, rhs.m_hitCount: %u", lhs.m_hitCount, rhs.m_hitCount); + return false; + } + + if (lhs.m_lineNumber != rhs.m_lineNumber) + { + AZ_Error("LineCoverage ==", false, "lhs.m_lineNumber: %u, rhs.m_lineNumber: %u", lhs.m_lineNumber, rhs.m_lineNumber); + return false; + } + + return true; + } + + bool operator==(const TestImpact::SourceCoverage& lhs, const TestImpact::SourceCoverage& rhs) + { + if (lhs.m_path != rhs.m_path) + { + AZ_Error("LineCoverage ==", false, "lhs.m_path: %s, rhs.m_path: %s", lhs.m_path.c_str(), rhs.m_path.c_str()); + return false; + } + + if (lhs.m_coverage.has_value() != rhs.m_coverage.has_value()) + { + AZ_Error( + "LineCoverage ==", false, "lhs.m_coverage.has_value(): %u, rhs.m_coverage.has_value(): %u", lhs.m_coverage.has_value(), + rhs.m_coverage.has_value()); + return false; + } + + if (lhs.m_coverage.has_value()) + { + return AZStd::equal( + lhs.m_coverage.value().begin(), lhs.m_coverage.value().end(), rhs.m_coverage.value().begin(), + [](const TestImpact::LineCoverage& left, const TestImpact::LineCoverage& right) { + return left == right; + }); + } + + return true; + } + + bool operator==(const TestImpact::ModuleCoverage& lhs, const TestImpact::ModuleCoverage& rhs) + { + if (lhs.m_path != rhs.m_path) + { + AZ_Error("ModuleCoverage ==", false, "lhs.m_path: %s, rhs.m_path: %s", lhs.m_path.c_str(), rhs.m_path.c_str()); + return false; + } + + return AZStd::equal( + lhs.m_sources.begin(), lhs.m_sources.end(), rhs.m_sources.begin(), + [](const TestImpact::SourceCoverage& left, const TestImpact::SourceCoverage& right) { + return left == right; + }); + } + + bool operator==(const AZStd::vector& lhs, const AZStd::vector& rhs) + { + if (lhs.size() != rhs.size()) + { + AZ_Error("ModuleCoverage ==", false, "lhs.size(): %u, rhs.size(): %u", lhs.size(), rhs.size()); + return false; + } + + return AZStd::equal( + lhs.begin(), lhs.end(), rhs.begin(), [](const TestImpact::ModuleCoverage& left, const TestImpact::ModuleCoverage& right) { + return left == right; + }); + } + + bool operator!=(const AZStd::vector& lhs, const AZStd::vector& rhs) + { + return !(lhs == rhs); + } + + bool operator==(const TestImpact::TestCoverage& lhs, const TestImpact::TestCoverage& rhs) + { + if (lhs.GetNumModulesCovered() != rhs.GetNumModulesCovered()) + { + return false; + } + + if (lhs.GetNumSourcesCovered() != rhs.GetNumSourcesCovered()) + { + return false; + } + + if (lhs.GetModuleCoverages() != rhs.GetModuleCoverages()) + { + return false; + } + + if (lhs.GetSourcesCovered().size() != rhs.GetSourcesCovered().size()) + { + return false; + } + + return true; + } +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestUtils.h b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestUtils.h new file mode 100644 index 0000000000..cc8d4190ec --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestImpactTestUtils.h @@ -0,0 +1,220 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace UnitTest +{ + // Common parameters for process related tests + inline constexpr const char* ValidProcessPath = LY_TEST_IMPACT_TEST_PROCESS_BIN; + inline constexpr const char* InvalidProcessPath = "!!!@@@---???"; + inline constexpr const AZStd::chrono::milliseconds LongSleep = AZStd::chrono::minutes(60); + inline constexpr const size_t LargeTextSize = 0xFFFF - 1; // 65,535 chars less the null terminator + inline constexpr const AZStd::chrono::milliseconds ShortSleep = AZStd::chrono::milliseconds(500); + inline constexpr const AZStd::chrono::milliseconds NoSleep = AZStd::chrono::milliseconds(0); + + // Writes the specified text string to the specified file + void WriteTextToFile(const AZStd::string& text, const AZ::IO::Path& path); + + // Construct the arguments for launcing the test process + AZStd::string ConstructTestProcessArgs(TestImpact::ProcessId pid, AZStd::chrono::milliseconds sleepTime); + + // Construct the arguments for launcing the test process with large text dump + AZStd::string ConstructTestProcessArgsLargeText(TestImpact::ProcessId pid, AZStd::chrono::milliseconds sleepTime); + + // Known standard output string of the test process + AZStd::string KnownTestProcessOutputString(TestImpact::ProcessId pid); + + // Known standard error string of the test process + AZStd::string KnownTestProcessErrorString(TestImpact::ProcessId pid); + + // Generate a gtest typed test fixture name string based on the specified name and type + AZStd::string GenerateTypedFixtureName(const AZStd::string& name, size_t typeNum); + + // Generate a gtest parameterized test fixture name string based on the specified name and permutation number + AZStd::string GenerateParameterizedFixtureName( + const AZStd::string& name, + const AZStd::optional& prefix = AZStd::nullopt); + + // Generate a gtest parameterized test name string based on the specified name and permutation number + AZStd::string GenerateParameterizedTestName(const AZStd::string& name, size_t testNum); + + // Generate a JSON string of array elements from the specified vector + AZStd::string StringVectorToJSONElements(const AZStd::vector strings); + + // Generate a build target descriptor string in JSON format from the specified build target description + AZStd::string GenerateBuildTargetDescriptorString( + const AZStd::string& name, + const AZStd::string& outputName, + const AZStd::string& path, + const AZStd::vector& staticSources, + const AZStd::vector& autogenInputs, + const AZStd::vector& autogenOutputs); + + // Generate a build target descriptor from the specified build target description + // Note: no check for correctness of arguments is peformed + TestImpact::BuildTargetDescriptor GenerateBuildTargetDescriptor( + const AZStd::string& name, + const AZStd::string& outputName, + const AZStd::string& path, + const AZStd::vector& staticSources, + const TestImpact::AutogenSources& autogenSources); + + // Procedurally generate a parameterized test suite based on the supplied parameters + TestImpact::TestEnumerationSuite GenerateParamterizedSuite( + const AZStd::pair& fixture, + const AZStd::optional& permutation, + const AZStd::vector> tests, + size_t permutationCount); + + // Procedurally generate a typed test suite based on the supplied parameters + void GenerateTypedSuite( + const AZStd::pair& fixture, + const AZStd::vector> tests, + size_t permutationCount, + AZStd::vector& parentSuiteList); + + // Helper functions for calculating test suite meta-data + size_t CalculateNumPassedTests(const AZStd::vector& suites); + size_t CalculateNumFailedTests(const AZStd::vector& suites); + size_t CalculateNumRunTests(const AZStd::vector& suites); + size_t CalculateNumNotRunTests(const AZStd::vector& suites); + + template + size_t CalculateNumTestSuites(const AZStd::vector& suites) + { + return suites.size(); + } + + template + size_t CalculateNumTests(const AZStd::vector& suites) + { + size_t numTests = 0; + for (const auto& suite : suites) + { + numTests += suite.m_tests.size(); + } + + return numTests; + } + + template + size_t CalculateNumEnabledTests(const AZStd::vector& suites) + { + size_t numEnabledTests = 0; + for (const auto& suite : suites) + { + if (!suite.m_enabled) + { + continue; + } + + for (const auto& test : suite.m_tests) + { + if (test.m_enabled) + { + numEnabledTests++; + } + } + } + + return numEnabledTests; + } + + template + size_t CalculateNumDisabledTests(const AZStd::vector& suites) + { + size_t numDisabledTests = 0; + for (const auto& suite : suites) + { + if (!suite.m_enabled) + { + numDisabledTests += suite.m_tests.size(); + continue; + } + + for (const auto& test : suite.m_tests) + { + if (!test.m_enabled) + { + numDisabledTests++; + } + } + } + + return numDisabledTests; + } + + // Test enumeration suite representation of the test targets used for testing + AZStd::vector GetTestTargetATestEnumerationSuites(); + AZStd::vector GetTestTargetBTestEnumerationSuites(); + AZStd::vector GetTestTargetCTestEnumerationSuites(); + AZStd::vector GetTestTargetDTestEnumerationSuites(); + + // Test run suite representation of the test targets used for testing + AZStd::vector GetTestTargetATestRunSuites(); + AZStd::vector GetTestTargetBTestRunSuites(); + AZStd::vector GetTestTargetCTestRunSuites(); + AZStd::vector GetTestTargetDTestRunSuites(); + + // Line coverage representation of the test targets used for testing + AZStd::vector GetTestTargetALineModuleCoverages(); + AZStd::vector GetTestTargetBLineModuleCoverages(); + AZStd::vector GetTestTargetCLineModuleCoverages(); + AZStd::vector GetTestTargetDLineModuleCoverages(); + + // Source coverage representation of the test targets used for testing + AZStd::vector GetTestTargetASourceModuleCoverages(); + AZStd::vector GetTestTargetBSourceModuleCoverages(); + AZStd::vector GetTestTargetCSourceModuleCoverages(); + AZStd::vector GetTestTargetDSourceModuleCoverages(); + + // Helper comparisons for test validation (could potentially be moved to production source in the future) + bool operator==(const TestImpact::TestEnumerationCase& lhs, const TestImpact::TestEnumerationCase& rhs); + bool operator==(const TestImpact::TestEnumerationSuite& lhs, const TestImpact::TestEnumerationSuite& rhs); + bool operator==(const AZStd::vector& lhs, const AZStd::vector& rhs); + bool operator==(const TestImpact::TestEnumeration& lhs, const TestImpact::TestEnumeration& rhs); + + bool operator==(const TestImpact::TestRunCase& lhs, const TestImpact::TestRunCase& rhs); + bool operator==(const TestImpact::TestRunSuite& lhs, const TestImpact::TestRunSuite& rhs); + bool operator==(const AZStd::vector& lhs, const AZStd::vector& rhs); + bool operator==(const TestImpact::TestRun& lhs, const TestImpact::TestRun& rhs); + bool CheckTestRunsAreEqualIgnoreDurations(const TestImpact::TestRun& lhs, const TestImpact::TestRun& rhs); + + bool operator==(const TestImpact::BuildMetaData& lhs, const TestImpact::BuildMetaData& rhs); + bool operator==(const TestImpact::TargetSources& lhs, const TestImpact::TargetSources& rhs); + bool operator==(const TestImpact::BuildTargetDescriptor& lhs, const TestImpact::BuildTargetDescriptor& rhs); + bool operator==(const TestImpact::TestTargetMeta& lhs, const TestImpact::TestTargetMeta& rhs); + bool operator==(const TestImpact::ProductionTargetDescriptor& lhs, const TestImpact::ProductionTargetDescriptor& rhs); + bool operator==(const TestImpact::TestTargetDescriptor& lhs, const TestImpact::TestTargetDescriptor& rhs); + + bool operator==(const TestImpact::LineCoverage& lhs, const TestImpact::LineCoverage& rhs); + bool operator==(const TestImpact::SourceCoverage& lhs, const TestImpact::SourceCoverage& rhs); + bool operator==(const TestImpact::ModuleCoverage& lhs, const TestImpact::ModuleCoverage& rhs); + bool operator==(const AZStd::vector& lhs, const AZStd::vector& rhs); + bool operator==(const TestImpact::TestCoverage& lhs, const TestImpact::TestCoverage& rhs); +} // namespace UnitTest diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/CMakeLists.txt b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/CMakeLists.txt new file mode 100644 index 0000000000..8298bb7123 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +add_subdirectory(Code) \ No newline at end of file diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/CMakeLists.txt b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/CMakeLists.txt new file mode 100644 index 0000000000..4b8e9d2809 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +ly_add_target( + NAME TestImpact.TestProcess.Console EXECUTABLE + NAMESPACE AZ + FILES_CMAKE + testimpactframework_testprocess_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Source + BUILD_DEPENDENCIES + PRIVATE + AZ::AzCore + AZ::AzFramework +) \ No newline at end of file diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcess.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcess.cpp new file mode 100644 index 0000000000..cdfad1404f --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcess.cpp @@ -0,0 +1,93 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include "TestImpactTestProcess.h" +#include "TestImpactTestProcessLargeText.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace TestImpact +{ + TestProcess::TestProcess(int argc, char* argv[]) + { + StartupEnvironment(); + ParseArgs(argc, argv); + } + + TestProcess::~TestProcess() + { + TeardownEnvironment(); + } + + void TestProcess::StartupEnvironment() + { + AZ::AllocatorInstance::Create(); + } + + void TestProcess::TeardownEnvironment() + { + AZ::AllocatorInstance::Destroy(); + } + + void TestProcess::ParseArgs(int argc, char* argv[]) + { + const AZStd::string idArg = "id"; + const AZStd::string sleepArg = "sleep"; + const AZStd::string largeArg = "large"; + + AZ::CommandLine commandLine; + commandLine.Parse(argc, argv); + + m_id = AZStd::stoi(commandLine.GetSwitchValue(idArg, 0)); + m_sleep = AZStd::stoi(commandLine.GetSwitchValue(sleepArg, 0)); + + m_dumpLargeText = false; + for (auto i = 0; i < commandLine.GetNumMiscValues(); i++) + { + const auto& miscValue = commandLine.GetMiscValue(i); + if (miscValue == largeArg) + { + m_dumpLargeText = true; + break; + } + } + } + + int TestProcess::MainFunc() + { + if (m_dumpLargeText) + { + // Dump the large text blob to stdout and stderr + std::cout << LongText; + std::cerr << LongText; + } + else + { + // Dump the short known output string with id appended to stdout and stderr + std::cout << "TestProcessMainStdOut" << m_id; + std::cerr << "TestProcessMainStdErr" << m_id; + } + + AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(m_sleep)); + + return m_id; + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcess.h b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcess.h new file mode 100644 index 0000000000..cfe18aa3df --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcess.h @@ -0,0 +1,38 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +namespace TestImpact +{ + class TestProcess + { + public: + TestProcess(int argc, char* argv[]); + ~TestProcess(); + + int MainFunc(); + + private: + void StartupEnvironment(); + void TeardownEnvironment(); + void ParseArgs(int argc, char* argv[]); + + int m_id; + int m_sleep; + bool m_dumpLargeText = false; + }; + +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcessLargeText.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcessLargeText.cpp new file mode 100644 index 0000000000..a4b899dd65 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcessLargeText.cpp @@ -0,0 +1,531 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include "TestImpactTestProcessLargeText.h" + +namespace TestImpact +{ + // Long text blob measuring 65,535 in length (inc. null terminator) + const char* const LongText = + "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"; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcessLargeText.h b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcessLargeText.h new file mode 100644 index 0000000000..faaabae2d3 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcessLargeText.h @@ -0,0 +1,19 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +namespace TestImpact +{ + // Large text blob in the form of a string literal so the app does not require the Az FileIO and Application environment + extern const char* const LongText; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcessMain.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcessMain.cpp new file mode 100644 index 0000000000..5c46ee525e --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/Source/TestImpactTestProcessMain.cpp @@ -0,0 +1,19 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include "TestImpactTestProcess.h" + +int main(int argc, char* argv[]) +{ + TestImpact::TestProcess process(argc, argv); + return process.MainFunc(); +} diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/testimpactframework_testprocess_files.cmake b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/testimpactframework_testprocess_files.cmake new file mode 100644 index 0000000000..a06f1fa706 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestProcess/Code/testimpactframework_testprocess_files.cmake @@ -0,0 +1,18 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + Source/TestImpactTestProcessMain.cpp + Source/TestImpactTestProcess.cpp + Source/TestImpactTestProcess.h + Source/TestImpactTestProcessLargeText.cpp + Source/TestImpactTestProcessLargeText.h +) diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/CMakeLists.txt b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/CMakeLists.txt new file mode 100644 index 0000000000..8298bb7123 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +add_subdirectory(Code) \ No newline at end of file diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/Code/CMakeLists.txt b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/Code/CMakeLists.txt new file mode 100644 index 0000000000..7748581284 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/Code/CMakeLists.txt @@ -0,0 +1,28 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +################################################################################ +# Tests +################################################################################ + +ly_add_target( + NAME TestImpact.TestTargetA.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE AZ + FILES_CMAKE + testimpactframework_testtargeta_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + BUILD_DEPENDENCIES + PRIVATE + AZ::AzTestShared + AZ::AzTest +) \ No newline at end of file diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/Code/Tests/TestImpactTestTargetA.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/Code/Tests/TestImpactTestTargetA.cpp new file mode 100644 index 0000000000..9598b88b98 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/Code/Tests/TestImpactTestTargetA.cpp @@ -0,0 +1,73 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +namespace UnitTest +{ + class TestFixture + : public AllocatorsTestFixture + { + }; + + TEST(TestCase, Test1_WillPass) + { + SUCCEED(); + } + + TEST(TestCase, Test2_WillPass) + { + SUCCEED(); + } + + TEST(TestCase, Test3_WillPass) + { + SUCCEED(); + } + + TEST(TestCase, Test4_WillPass) + { + SUCCEED(); + } + + TEST(TestCase, Test5_WillPass) + { + SUCCEED(); + } + + TEST(TestCase, Test6_WillPass) + { + SUCCEED(); + } + + TEST(TestCase, Test7_WillFail) + { + FAIL(); + } + + TEST_F(TestFixture, Test1_WillPass) + { + SUCCEED(); + } + + TEST_F(TestFixture, Test2_WillPass) + { + SUCCEED(); + } + + TEST_F(TestFixture, Test3_WillPass) + { + SUCCEED(); + } +} // namespace UnitTest + +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/Code/testimpactframework_testtargeta_tests_files.cmake b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/Code/testimpactframework_testtargeta_tests_files.cmake new file mode 100644 index 0000000000..32c8746c9d --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetA/Code/testimpactframework_testtargeta_tests_files.cmake @@ -0,0 +1,14 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + Tests/TestImpactTestTargetA.cpp +) diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/CMakeLists.txt b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/CMakeLists.txt new file mode 100644 index 0000000000..8298bb7123 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +add_subdirectory(Code) \ No newline at end of file diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/Code/CMakeLists.txt b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/Code/CMakeLists.txt new file mode 100644 index 0000000000..57b55fb9f4 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/Code/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +################################################################################ +# Tests +################################################################################ + +ly_add_target( + NAME TestImpact.TestTargetB.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE AZ + FILES_CMAKE + testimpactframework_testtargetb_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + BUILD_DEPENDENCIES + PRIVATE + AZ::AzCore + AZ::AzTestShared + AZ::AzTest +) \ No newline at end of file diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/Code/Tests/TestImpactTestTargetB.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/Code/Tests/TestImpactTestTargetB.cpp new file mode 100644 index 0000000000..be58b4fa17 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/Code/Tests/TestImpactTestTargetB.cpp @@ -0,0 +1,78 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +namespace UnitTest +{ + class TestFixture + : public AllocatorsTestFixture + { + }; + + class TestFixtureWithParams + : public AllocatorsTestFixture + , public ::testing::WithParamInterface> + { + }; + + TEST(TestCase, Test1_WillPass) + { + SUCCEED(); + } + + TEST(TestCase, Test2_WillPass) + { + SUCCEED(); + } + + TEST(TestCase, Test3_WillPass) + { + SUCCEED(); + } + + TEST_F(TestFixture, Test1_WillPass) + { + SUCCEED(); + } + + TEST_P(TestFixtureWithParams, Test1_WillPass) + { + SUCCEED(); + } + + TEST_P(TestFixtureWithParams, Test2_WillPass) + { + SUCCEED(); + } + + INSTANTIATE_TEST_CASE_P( + PermutationA, + TestFixtureWithParams, + ::testing::Combine( + ::testing::Values(1, 2, 4), + ::testing::Values(3, 5, 7), + ::testing::Values(-0.0f, 0.0f, 1.0f) + )); + + INSTANTIATE_TEST_CASE_P( + , + TestFixtureWithParams, + ::testing::Combine( + ::testing::Values(8, 16, 32), + ::testing::Values(9, 13, 17), + ::testing::Values(-10.0f, 0.05f, 10.0f) + )); +} // namespace UnitTest + +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/Code/testimpactframework_testtargetb_tests_files.cmake b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/Code/testimpactframework_testtargetb_tests_files.cmake new file mode 100644 index 0000000000..949074a5af --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetB/Code/testimpactframework_testtargetb_tests_files.cmake @@ -0,0 +1,14 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + Tests/TestImpactTestTargetB.cpp +) diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/CMakeLists.txt b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/CMakeLists.txt new file mode 100644 index 0000000000..8298bb7123 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +add_subdirectory(Code) \ No newline at end of file diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/Code/CMakeLists.txt b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/Code/CMakeLists.txt new file mode 100644 index 0000000000..47862f80df --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/Code/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +################################################################################ +# Tests +################################################################################ + +ly_add_target( + NAME TestImpact.TestTargetC.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE AZ + FILES_CMAKE + testimpactframework_testtargetc_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + BUILD_DEPENDENCIES + PRIVATE + AZ::AzCore + AZ::AzTestShared + AZ::AzTest +) \ No newline at end of file diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/Code/Tests/TestImpactTestTargetC.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/Code/Tests/TestImpactTestTargetC.cpp new file mode 100644 index 0000000000..48f0ffe9dc --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/Code/Tests/TestImpactTestTargetC.cpp @@ -0,0 +1,63 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +namespace UnitTest +{ + class TestFixture + : public AllocatorsTestFixture + { + }; + + template + class TestFixtureWithTypes + : public AllocatorsTestFixture + { + }; + + using TestTypes = testing::Types; + TYPED_TEST_CASE(TestFixtureWithTypes, TestTypes); + + TEST_F(TestFixture, Test1_WillPass) + { + SUCCEED(); + } + + TEST_F(TestFixture, Test2_WillPass) + { + SUCCEED(); + } + + TYPED_TEST(TestFixtureWithTypes, Test1_WillPass) + { + SUCCEED(); + } + + TYPED_TEST(TestFixtureWithTypes, Test2_WillPass) + { + SUCCEED(); + } + + TYPED_TEST(TestFixtureWithTypes, Test3_WillPass) + { + SUCCEED(); + } + + TYPED_TEST(TestFixtureWithTypes, Test4_WillPass) + { + SUCCEED(); + } +} // namespace UnitTest + +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/Code/testimpactframework_testtargetc_tests_files.cmake b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/Code/testimpactframework_testtargetc_tests_files.cmake new file mode 100644 index 0000000000..fa5b4f6e9a --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetC/Code/testimpactframework_testtargetc_tests_files.cmake @@ -0,0 +1,14 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + Tests/TestImpactTestTargetC.cpp +) diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/CMakeLists.txt b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/CMakeLists.txt new file mode 100644 index 0000000000..8298bb7123 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +add_subdirectory(Code) \ No newline at end of file diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/Code/CMakeLists.txt b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/Code/CMakeLists.txt new file mode 100644 index 0000000000..bf821ac0d6 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/Code/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +################################################################################ +# Tests +################################################################################ + +ly_add_target( + NAME TestImpact.TestTargetD.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE AZ + FILES_CMAKE + testimpactframework_testtargetd_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + BUILD_DEPENDENCIES + PRIVATE + AZ::AzCore + AZ::AzTestShared + AZ::AzTest +) \ No newline at end of file diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/Code/Tests/TestImpactTestTargetD.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/Code/Tests/TestImpactTestTargetD.cpp new file mode 100644 index 0000000000..bd27b51fd9 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/Code/Tests/TestImpactTestTargetD.cpp @@ -0,0 +1,188 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +namespace UnitTest +{ + class TestFixture1 + : public AllocatorsTestFixture + { + }; + + class DISABLED_TestFixture2 + : public AllocatorsTestFixture + { + }; + + template + class TestFixtureWithTypes1 + : public AllocatorsTestFixture + { + }; + + template + class DISABLED_TestFixtureWithTypes2 + : public AllocatorsTestFixture + { + }; + + class TestFixtureWithParams1 + : public AllocatorsTestFixture + , public ::testing::WithParamInterface> + { + }; + + class DISABLED_TestFixtureWithParams2 + : public AllocatorsTestFixture + , public ::testing::WithParamInterface> + { + }; + + using TestTypes = testing::Types; + TYPED_TEST_CASE(TestFixtureWithTypes1, TestTypes); + TYPED_TEST_CASE(DISABLED_TestFixtureWithTypes2, TestTypes); + + TEST(TestCase, Test1_WillPass) + { + SUCCEED(); + } + + TEST(TestCase, DISABLED_Test2_WillPass) + { + SUCCEED(); + } + + TEST(TestCase, Test3_WillPass) + { + SUCCEED(); + } + + TEST(TestCase, Test4_WillPass) + { + SUCCEED(); + } + + TEST(TestCase, Test5_WillPass) + { + SUCCEED(); + } + + TEST_F(TestFixture1, Test1_WillPass) + { + SUCCEED(); + } + + TEST_F(TestFixture1, Test2_WillPass) + { + SUCCEED(); + } + + TEST_F(DISABLED_TestFixture2, Test1_WillPass) + { + SUCCEED(); + } + + TEST_F(DISABLED_TestFixture2, Test2_WillPass) + { + SUCCEED(); + } + + TEST_P(TestFixtureWithParams1, Test1_WillPass) + { + SUCCEED(); + } + + TEST_P(TestFixtureWithParams1, DISABLED_Test2_WillPass) + { + SUCCEED(); + } + + TEST_P(DISABLED_TestFixtureWithParams2, Test1_WillPass) + { + SUCCEED(); + } + + TEST_P(DISABLED_TestFixtureWithParams2, DISABLED_Test2_WillPass) + { + SUCCEED(); + } + + INSTANTIATE_TEST_CASE_P( + PermutationA, + TestFixtureWithParams1, + ::testing::Combine( + ::testing::Values(1, 2, 4), + ::testing::Values(3, 5, 7), + ::testing::Values(-0.0f, 0.0f, 1.0f) + )); + + INSTANTIATE_TEST_CASE_P( + , + TestFixtureWithParams1, + ::testing::Combine( + ::testing::Values(8, 16, 32), + ::testing::Values(9, 13, 17), + ::testing::Values(-10.0f, 0.05f, 10.0f) + )); + + INSTANTIATE_TEST_CASE_P( + PermutationA, + DISABLED_TestFixtureWithParams2, + ::testing::Combine( + ::testing::Values(1, 2, 4), + ::testing::Values(3, 5, 7), + ::testing::Values(-0.0f, 0.0f, 1.0f) + )); + + INSTANTIATE_TEST_CASE_P( + , + DISABLED_TestFixtureWithParams2, + ::testing::Combine( + ::testing::Values(8, 16, 32), + ::testing::Values(9, 13, 17), + ::testing::Values(-10.0f, 0.05f, 10.0f) + )); + + TYPED_TEST(TestFixtureWithTypes1, Test1_WillPass) + { + SUCCEED(); + } + + TYPED_TEST(TestFixtureWithTypes1, DISABLED_Test2_WillPass) + { + SUCCEED(); + } + + TYPED_TEST(TestFixtureWithTypes1, Test3_WillPass) + { + SUCCEED(); + } + + TYPED_TEST(DISABLED_TestFixtureWithTypes2, Test1_WillPass) + { + SUCCEED(); + } + + TYPED_TEST(DISABLED_TestFixtureWithTypes2, DISABLED_Test2_WillPass) + { + SUCCEED(); + } + + TYPED_TEST(DISABLED_TestFixtureWithTypes2, Test3_WillPass) + { + SUCCEED(); + } +} // namespace UnitTest + +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/Code/testimpactframework_testtargetd_tests_files.cmake b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/Code/testimpactframework_testtargetd_tests_files.cmake new file mode 100644 index 0000000000..ebb5a422c7 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Tests/TestTargetD/Code/testimpactframework_testtargetd_tests_files.cmake @@ -0,0 +1,14 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + Tests/TestImpactTestTargetD.cpp +) diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/testimpactframework_runtime_files.cmake b/Code/Tools/TestImpactFramework/Runtime/Code/testimpactframework_runtime_files.cmake index 74c7c84dcc..fd65a16e39 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/testimpactframework_runtime_files.cmake +++ b/Code/Tools/TestImpactFramework/Runtime/Code/testimpactframework_runtime_files.cmake @@ -10,5 +10,20 @@ # set(FILES - Source/Dummy.cpp + Include/TestImpactFramework/TestImpactException.h + Include/TestImpactFramework/TestImpactFrameworkPath.h + Include/TestImpactFramework/TestImpactCallback.h + Source/TestImpactException.cpp + Source/TestImpactFrameworkPath.cpp + Source/Process/TestImpactProcess.cpp + Source/Process/TestImpactProcess.h + Source/Process/TestImpactProcessException.h + Source/Process/TestImpactProcessInfo.cpp + Source/Process/TestImpactProcessInfo.h + Source/Process/TestImpactProcessLauncher.h + Source/Process/JobRunner/TestImpactProcessJob.h + Source/Process/JobRunner/TestImpactProcessJobInfo.h + Source/Process/JobRunner/TestImpactProcessJobRunner.h + Source/Process/Scheduler/TestImpactProcessScheduler.cpp + Source/Process/Scheduler/TestImpactProcessScheduler.h ) diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/testimpactframework_runtime_tests_files.cmake b/Code/Tools/TestImpactFramework/Runtime/Code/testimpactframework_runtime_tests_files.cmake new file mode 100644 index 0000000000..61059dc543 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/testimpactframework_runtime_tests_files.cmake @@ -0,0 +1,22 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + Tests/Process/TestImpactProcessSchedulerTest.cpp + Tests/Process/TestImpactProcessTest.cpp + Tests/TestImpactExceptionTest.cpp + Tests/TestImpactFrameworkPathTest.cpp + Tests/TestImpactProcessSchedulerTest.cpp + Tests/TestImpactProcessTest.cpp + Tests/TestImpactProcessTestShared.cpp + Tests/TestImpactProcessTestShared.h + Tests/TestImpactTestMain.cpp +)