Pārlūkot izejas kodu

Merge branch 'upstream/development' into genewalt/gitflow_210628

Signed-off-by: Gene Walters <[email protected]>
Gene Walters 4 gadi atpakaļ
vecāks
revīzija
6fb2558e44
39 mainītis faili ar 1034 papildinājumiem un 176 dzēšanām
  1. 73 0
      .github/ISSUE_TEMPLATE/rfc-feature.md
  2. 61 0
      .github/ISSUE_TEMPLATE/rfc-suggestion.md
  3. 1 0
      AutomatedTesting/Gem/CMakeLists.txt
  4. 1 0
      AutomatedTesting/Gem/Code/enabled_gems.cmake
  5. 12 0
      AutomatedTesting/Gem/PythonCoverage/CMakeLists.txt
  6. 62 0
      AutomatedTesting/Gem/PythonCoverage/Code/CMakeLists.txt
  7. 12 0
      AutomatedTesting/Gem/PythonCoverage/Code/Platform/Android/PAL_android.cmake
  8. 12 0
      AutomatedTesting/Gem/PythonCoverage/Code/Platform/Linux/PAL_linux.cmake
  9. 12 0
      AutomatedTesting/Gem/PythonCoverage/Code/Platform/Mac/PAL_mac.cmake
  10. 12 0
      AutomatedTesting/Gem/PythonCoverage/Code/Platform/Windows/PAL_windows.cmake
  11. 12 0
      AutomatedTesting/Gem/PythonCoverage/Code/Platform/iOS/PAL_ios.cmake
  12. 38 0
      AutomatedTesting/Gem/PythonCoverage/Code/Source/PythonCoverageEditorModule.cpp
  13. 33 0
      AutomatedTesting/Gem/PythonCoverage/Code/Source/PythonCoverageEditorModule.h
  14. 246 0
      AutomatedTesting/Gem/PythonCoverage/Code/Source/PythonCoverageEditorSystemComponent.cpp
  15. 88 0
      AutomatedTesting/Gem/PythonCoverage/Code/Source/PythonCoverageEditorSystemComponent.h
  16. 15 0
      AutomatedTesting/Gem/PythonCoverage/Code/pythoncoverage_editor_files.cmake
  17. 15 0
      AutomatedTesting/Gem/PythonCoverage/Code/pythoncoverage_editor_shared_files.cmake
  18. 13 0
      AutomatedTesting/Gem/PythonCoverage/gem.json
  19. 3 0
      AutomatedTesting/Gem/PythonCoverage/preview.png
  20. 1 1
      AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py
  21. 1 1
      AutomatedTesting/Gem/PythonTests/automatedtesting_shared/base.py
  22. 10 4
      Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonRunnerRequestsBus.h
  23. 45 0
      Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonScriptNotificationsBus.h
  24. 12 3
      Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTreeView.cpp
  25. 3 0
      Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTreeView.h
  26. 1 0
      Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake
  27. 9 1
      Code/Sandbox/Editor/CryEdit.cpp
  28. 3 0
      Code/Tools/TestImpactFramework/Frontend/Console/Code/CMakeLists.txt
  29. 41 28
      Gems/Atom/RHI/Vulkan/Code/Source/RHI/Device.cpp
  30. 4 0
      Gems/Atom/RHI/Vulkan/Code/Source/RHI/Device.h
  31. 67 1
      Gems/Atom/RHI/Vulkan/Code/Source/RHI/PhysicalDevice.cpp
  32. 28 0
      Gems/Atom/RHI/Vulkan/Code/Source/RHI/PhysicalDevice.h
  33. 11 1
      Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp
  34. 1 1
      Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.h
  35. 3 116
      cmake/TestImpactFramework/ConsoleFrontendConfig.in
  36. 7 0
      cmake/TestImpactFramework/EnumeratedGemTargets.in
  37. 60 15
      cmake/TestImpactFramework/LYTestImpactFramework.cmake
  38. 1 1
      scripts/build/Jenkins/Jenkinsfile
  39. 5 3
      scripts/build/Platform/Windows/build_config.json

+ 73 - 0
.github/ISSUE_TEMPLATE/rfc-feature.md

@@ -0,0 +1,73 @@
+---
+name: RFC Feature request
+about: Create Feature RFC for this project
+title: Proposed RFC Feature =description=
+labels: 'rfc-feature'
+assignees: ''
+
+---
+
+# O3DE RFC Feature Template
+
+### When using this template, you do not have to fill out every question below. The questions are there for guidance.
+
+This RFC feature template should be used for any feature that is not a bug or a substantial reorganization of the O3DE product.
+
+If you submit a pull request to implement a new feature without going through the RFC process, it may be closed with a polite request to submit an RFC first.
+
+A hastily proposed RFC can hurt its chances of acceptance. Low-quality proposals, proposals for previously-rejected features, or those that don't fit into the near-term roadmap may be quickly rejected, demotivating the unprepared contributor. Laying some groundwork ahead of the RFC can make the process smoother.
+
+Although there is no single way to prepare for submitting an RFC, it is generally a good idea to pursue feedback from other project developers beforehand to ascertain that the RFC may be desirable; having a consistent impact on the project requires concerted effort toward consensus-building.
+
+The most common preparations for writing and submitting an RFC include:
+- Talking the idea over on our Discord server.
+- Discussing the topic on our GitHub RFCs discussions page.
+- Occasionally posting "pre-RFCs" on the GitHub RFCs discussion page.
+You may file issues in the RFCs repo for discussion, but these are not actively looked at by the teams.
+
+As a rule of thumb, receiving encouraging feedback from long-standing project developers, and particularly members of the relevant sub-team, is a good indication that the RFC is worth pursuing.
+
+# ----- DELETE EVERYTHING FROM THE TOP TO THE SUMMARY LINE BELOW WHEN USING TEMPLATE ----- #
+
+### Summary:
+Single paragraph explanation of the feature
+
+### What is the relevance of this feature?
+Why is this important? What are the use cases? What will it do once completed?
+
+### Feature design description:
+- Explain the design of the feature with enough detail that someone familiar with the environment and framework can understand the concept and explain it to others. 
+- It should include at least one end-to-end example of how a developer will use it along with specific details, including outlying use cases. 
+
+- If there is any new terminology, it should be defined here.
+
+### Technical design description:
+- Explain the technical portion of the work in enough detail that members can implement the feature. 
+
+- Explain any API or process changes required to implement this feature
+
+- This section should relate to the feature design description by reference and explain in greater detail how it makes the feature design examples work.
+
+- This should also provide detailed information on compatibility with different hardware platforms.
+
+
+### What are the advantages of the feature?
+- Explain the advantages for someone to use this feature
+
+### What are the disadvantages of the feature?
+- Explain any disadvantages for someone to use this feature
+
+### How will this be implemented or integrated into the O3DE environment?
+- Explain how a developer will integrate this into the codebase of O3DE and provide any specific library or technical stack requirements.
+
+### Are there any alternatives to this feature?
+- Provide any other designs that have been considered. Explain what the impact might be of not doing this.
+- If there is any prior art or approaches with other frameworks in the same domain, explain how they may have solved this problem or implemented this feature.
+
+### How will users learn this feature?
+- Detail how it can be best presented and how it is used as an extension or a standalone tool used with O3DE.
+- Explain if and how it may change how individuals would use the platform and if any documentation must be changed or reorganized.
+- Explain how it would be taught to new and existing O3DE users.
+
+### Are there any open questions?
+- What are some of the open questions and potential scenarios that should be considered?

+ 61 - 0
.github/ISSUE_TEMPLATE/rfc-suggestion.md

@@ -0,0 +1,61 @@
+---
+name: RFC Suggestion request
+about: Create Suggestion RFC for this project
+title: Proposed RFC Suggestion =description=
+labels: 'rfc-suggestion'
+assignees: ''
+
+---
+
+# O3DE Suggestion RFC Template
+
+### When using this template, you do not have to fill out every question below. The questions are there for guidance.
+
+This RFC template should be used for any suggestion that is not based upon code or content related to the O3DE product itself. This template is for proposing new process models, approaches, or ideas to improve the O3DE community.
+
+A hastily proposed RFC can hurt its chances of acceptance. Low-quality proposals, proposals for previously rejected features, or those that do not have any substantive value to the project may be quickly rejected, demotivating the unprepared contributor. Laying some groundwork with others in the community ahead of the RFC can make the process much smoother.
+
+Although there is no single way to prepare for submitting an RFC, it is generally a good idea to pursue feedback from other project members beforehand. Keep in mind that you want other members to contribute and back your suggestion, which can drastically improve the chances of implementation.
+
+The most common preparations for writing and submitting an RFC include:
+- Talking the idea over on our Discord server.
+- Creating a discussion on our GitHub RFCs discussions page.
+- Occasionally posting "pre-RFCs" on the GitHub RFCs discussion page.
+You may file issues in the RFCs repo for discussion, but these are not actively looked at by the teams.
+
+As a rule of thumb, receiving encouraging feedback from long-standing community members is a good indication that the RFC is worth pursuing.
+
+# ----- DELETE EVERYTHING FROM THE TOP TO THE SUMMARY LINE BELOW WHEN USING TEMPLATE ----- #
+
+### Summary:
+Single paragraph explanation of the suggestion
+
+### What is the motivation for this suggestion?
+Why is this important? 
+What are the use cases for this suggestion?
+What should the outcome be if this suggestion is implemented?
+
+### Suggestion design description:
+- Explain the suggestion with enough detail that someone familiar with the process and environment of the project can understand the suggestion and explain it to others.
+- It should include at least one end-to-end example of how the community will use it along with the specific details with outlying use cases. 
+
+- If there is any new terminology, it should be defined here.
+
+### What are the advantages of the suggestion?
+- Explain the advantages of using this suggestion
+
+### What are the disadvantages of the suggestion?
+- Explain any disadvantages or trade-offs to using this suggestion
+
+### How will this be work within the O3DE project?
+- Explain how this suggestion will be work within the O3DE project.
+
+### Are there any alternatives to this suggestion?
+- Provide any other alternative ways that have been considered. 
+- Explain what the impact might be of not implementing this suggestion.
+- If there are other similar suggestions previously used, list them and explain which parts may have solved some or all of this problem.
+
+### What is the strategy for adoption?
+- Explain how new and existing users will adopt this suggestion.
+- Point out any efforts needed if it requires coordination with multiple SIGs or other projects. 
+- Explain how it would be taught to new and existing O3DE users.

+ 1 - 0
AutomatedTesting/Gem/CMakeLists.txt

@@ -7,3 +7,4 @@
 
 add_subdirectory(Code)
 add_subdirectory(PythonTests)
+add_subdirectory(PythonCoverage)

+ 1 - 0
AutomatedTesting/Gem/Code/enabled_gems.cmake

@@ -48,6 +48,7 @@ set(ENABLED_GEMS
     LyShine
     HttpRequestor
     Atom_AtomBridge
+    PythonCoverage
 )
 
 # TODO remove conditional add once AWSNativeSDK libs are fixed for Android and Linux Monolithic release.

+ 12 - 0
AutomatedTesting/Gem/PythonCoverage/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)

+ 62 - 0
AutomatedTesting/Gem/PythonCoverage/Code/CMakeLists.txt

@@ -0,0 +1,62 @@
+#
+# 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_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} ${o3de_gem_restricted_path} ${o3de_gem_path} ${o3de_gem_name})
+include(${pal_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
+
+if(PAL_TRAIT_PYTHONCOVERAGE_SUPPORTED)
+    if(PAL_TRAIT_BUILD_HOST_TOOLS)
+        ly_add_target(
+            NAME PythonCoverage.Editor.Static STATIC
+            NAMESPACE Gem
+            FILES_CMAKE
+                pythoncoverage_editor_files.cmake
+            INCLUDE_DIRECTORIES
+                PRIVATE
+                    Source
+                PUBLIC
+                    Include
+            COMPILE_DEFINITIONS
+                PUBLIC
+                    PYTHON_COVERAGE_EDITOR
+                PRIVATE
+                    ${LY_TEST_IMPACT_CONFIG_FILE_PATH_DEFINITION}
+            BUILD_DEPENDENCIES
+                PUBLIC
+                    AZ::AzToolsFramework
+            RUNTIME_DEPENDENCIES
+                Gem::EditorPythonBindings.Editor
+        )
+
+        ly_add_target(
+            NAME PythonCoverage.Editor GEM_MODULE
+            NAMESPACE Gem
+            AUTOMOC
+            OUTPUT_NAME Gem.PythonCoverage.Editor
+            FILES_CMAKE
+                pythoncoverage_editor_shared_files.cmake
+            COMPILE_DEFINITIONS
+                PRIVATE
+                    PYTHON_COVERAGE_EDITOR
+            INCLUDE_DIRECTORIES
+                PRIVATE
+                    Source
+                PUBLIC
+                    Include
+            BUILD_DEPENDENCIES
+                PUBLIC
+                    Gem::PythonCoverage.Editor.Static
+        )
+
+        ly_create_alias(NAME PythonCoverage.Tools    NAMESPACE Gem TARGETS Gem::PythonCoverage.Editor)
+        ly_create_alias(NAME PythonCoverage.Builders NAMESPACE Gem TARGETS Gem::PythonCoverage.Editor)
+    endif()
+endif()

+ 12 - 0
AutomatedTesting/Gem/PythonCoverage/Code/Platform/Android/PAL_android.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(PAL_TRAIT_PYTHONCOVERAGE_SUPPORTED FALSE)

+ 12 - 0
AutomatedTesting/Gem/PythonCoverage/Code/Platform/Linux/PAL_linux.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(PAL_TRAIT_PYTHONCOVERAGE_SUPPORTED FALSE)

+ 12 - 0
AutomatedTesting/Gem/PythonCoverage/Code/Platform/Mac/PAL_mac.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(PAL_TRAIT_PYTHONCOVERAGE_SUPPORTED FALSE)

+ 12 - 0
AutomatedTesting/Gem/PythonCoverage/Code/Platform/Windows/PAL_windows.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(PAL_TRAIT_PYTHONCOVERAGE_SUPPORTED TRUE)

+ 12 - 0
AutomatedTesting/Gem/PythonCoverage/Code/Platform/iOS/PAL_ios.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(PAL_TRAIT_PYTHONCOVERAGE_SUPPORTED FALSE)

+ 38 - 0
AutomatedTesting/Gem/PythonCoverage/Code/Source/PythonCoverageEditorModule.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 "PythonCoverageEditorModule.h"
+#include "PythonCoverageEditorSystemComponent.h"
+
+namespace PythonCoverage
+{
+    AZ_CLASS_ALLOCATOR_IMPL(PythonCoverageEditorModule, AZ::SystemAllocator, 0)
+
+    PythonCoverageEditorModule::PythonCoverageEditorModule()
+    {
+        m_descriptors.insert(
+            m_descriptors.end(),
+            {
+                PythonCoverageEditorSystemComponent::CreateDescriptor()
+            });
+    }
+
+    PythonCoverageEditorModule::~PythonCoverageEditorModule() = default;
+
+    AZ::ComponentTypeList PythonCoverageEditorModule::GetRequiredSystemComponents() const
+    {
+        // add required SystemComponents to the SystemEntity
+        return AZ::ComponentTypeList{ azrtti_typeid<PythonCoverageEditorSystemComponent>() };
+    }
+} // namespace PythonCoverage
+
+AZ_DECLARE_MODULE_CLASS(Gem_PythonCoverageEditor, PythonCoverage::PythonCoverageEditorModule)

+ 33 - 0
AutomatedTesting/Gem/PythonCoverage/Code/Source/PythonCoverageEditorModule.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 <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Module/Module.h>
+
+namespace PythonCoverage
+{
+    class PythonCoverageEditorModule
+        : public AZ::Module
+    {
+    public:
+        AZ_CLASS_ALLOCATOR_DECL
+        AZ_RTTI(PythonCoverageEditorModule, "{32C0FFEA-09A7-460F-9257-5BDEF74FCD5B}", AZ::Module);
+
+        PythonCoverageEditorModule();
+        ~PythonCoverageEditorModule();
+
+        // PythonCoverageModule ...
+        AZ::ComponentTypeList GetRequiredSystemComponents() const override;
+    };
+} // namespace WhiteBox

+ 246 - 0
AutomatedTesting/Gem/PythonCoverage/Code/Source/PythonCoverageEditorSystemComponent.cpp

@@ -0,0 +1,246 @@
+/*
+ * 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 <AzCore/IO/Path/Path.h>
+#include <AzCore/JSON/document.h>
+#include <AzCore/Module/ModuleManagerBus.h>
+#include <AzCore/Module/Module.h>
+#include <AzCore/Module/DynamicModuleHandle.h>
+#include <AzCore/Serialization/SerializeContext.h>
+
+#include <PythonCoverageEditorSystemComponent.h>
+
+namespace PythonCoverage
+{
+    static constexpr char* const LogCallSite = "PythonCoverageEditorSystemComponent";
+
+    void PythonCoverageEditorSystemComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<PythonCoverageEditorSystemComponent, AZ::Component>()->Version(1);
+        }
+    }
+
+    void PythonCoverageEditorSystemComponent::Activate()
+    {
+        AzToolsFramework::EditorPythonScriptNotificationsBus::Handler::BusConnect();
+        AZ::EntitySystemBus::Handler::BusConnect();
+
+        // If no output directory discovered, coverage gathering will be disabled
+        if (ParseCoverageOutputDirectory() == CoverageState::Disabled)
+        {
+            return;
+        }
+
+        EnumerateAllModuleComponents();
+    }
+
+    void PythonCoverageEditorSystemComponent::Deactivate()
+    {
+        AZ::EntitySystemBus::Handler::BusDisconnect();
+        AzToolsFramework::EditorPythonScriptNotificationsBus::Handler::BusDisconnect();
+    }
+
+    void PythonCoverageEditorSystemComponent::OnEntityActivated(const AZ::EntityId& entityId)
+    {
+        if (m_coverageState == CoverageState::Disabled)
+        {
+            return;
+        }
+
+        EnumerateComponentsForEntity(entityId);
+
+        // There is currently no way to receive a graceful exit signal in order to properly handle the coverage end of life so
+        // instead we have to serialize the data on-the-fly with blocking disk writes on the main thread... if this adversely
+        // affects performance in a measurable way then this could potentially be put on a worker thread, although it remains to
+        // be seen whether the asynchronous nature of such a thread results in queued up coverage being lost due to the hard exit
+        if (m_coverageState == CoverageState::Gathering)
+        {
+            WriteCoverageFile();
+        }
+    }
+
+    PythonCoverageEditorSystemComponent::CoverageState PythonCoverageEditorSystemComponent::ParseCoverageOutputDirectory()
+    {
+        m_coverageState = CoverageState::Disabled;
+        const AZStd::string configFilePath = LY_TEST_IMPACT_DEFAULT_CONFIG_FILE;
+
+        if (configFilePath.empty())
+        {
+            AZ_Warning(LogCallSite, false, "No test impact analysis framework config file specified.");
+            return m_coverageState;
+        }
+
+        const auto fileSize = AZ::IO::SystemFile::Length(configFilePath.c_str());
+        if(!fileSize)
+        {
+            AZ_Error(LogCallSite, false, "Test impact analysis framework config file '%s' does not exist", configFilePath.c_str());
+            return m_coverageState;
+        }
+
+        AZStd::vector<char> buffer(fileSize + 1);
+        buffer[fileSize] = '\0';
+        if (!AZ::IO::SystemFile::Read(configFilePath.c_str(), buffer.data()))
+        {
+            AZ_Error(LogCallSite, false, "Could not read contents of test impact analysis framework config file '%s'", configFilePath.c_str());
+            return m_coverageState;
+        }
+        
+        const AZStd::string configurationData = AZStd::string(buffer.begin(), buffer.end());
+        rapidjson::Document configurationFile;
+        if (configurationFile.Parse(configurationData.c_str()).HasParseError())
+        {
+            AZ_Error(LogCallSite, false, "Could not parse test impact analysis framework config file data, JSON has errors");
+            return m_coverageState;
+        }
+
+        const auto& tempConfig = configurationFile["workspace"]["temp"];
+
+        // Temp directory root path is absolute
+        const AZ::IO::Path tempWorkspaceRootDir = tempConfig["root"].GetString();
+
+        // Artifact directory is relative to temp directory root
+        const AZ::IO::Path artifactRelativeDir = tempConfig["relative_paths"]["artifact_dir"].GetString();
+        m_coverageDir = tempWorkspaceRootDir / artifactRelativeDir;
+
+        // Everything is good to go, await the first python test case
+        m_coverageState = CoverageState::Idle;
+        return m_coverageState;
+    }
+    
+    void PythonCoverageEditorSystemComponent::WriteCoverageFile()
+    {
+        AZStd::string contents;
+
+        // Compile the coverage for all test cases in this script
+        for (const auto& [testCase, entityComponents] : m_entityComponentMap)
+        {
+            const auto coveringModules = GetParentComponentModulesForAllActivatedEntities(entityComponents);
+            if (coveringModules.empty())
+            {
+                return;
+            }
+
+            contents = testCase + "\n";
+            for (const auto& coveringModule : coveringModules)
+            {
+                contents += AZStd::string::format(" %s\n", coveringModule.c_str());
+            }
+        }
+    
+        AZ::IO::SystemFile file;
+        const AZStd::vector<char> bytes(contents.begin(), contents.end());
+        if (!file.Open(
+                m_coverageFile.c_str(),
+                AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY))
+        {
+            AZ_Error(LogCallSite, false, "Couldn't open file '%s' for writing", m_coverageFile.c_str());
+            return;
+        }
+    
+        if (!file.Write(bytes.data(), bytes.size()))
+        {
+            AZ_Error(LogCallSite, false, "Couldn't write contents for file '%s'", m_coverageFile.c_str());
+            return;
+        }
+    }
+    
+    void PythonCoverageEditorSystemComponent::EnumerateAllModuleComponents()
+    {
+        AZ::ModuleManagerRequestBus::Broadcast(
+            &AZ::ModuleManagerRequestBus::Events::EnumerateModules,
+            [this](const AZ::ModuleData& moduleData)
+            {
+                // We can only enumerate shared libs, static libs are invisible to us
+                if (moduleData.GetDynamicModuleHandle())
+                {
+                    for (const auto* moduleComponentDescriptor : moduleData.GetModule()->GetComponentDescriptors())
+                    {
+                        m_moduleComponents[moduleComponentDescriptor->GetUuid()] = moduleData.GetDebugName();
+                    }
+                }
+    
+                return true;
+            });
+    }
+    
+    void PythonCoverageEditorSystemComponent::EnumerateComponentsForEntity(const AZ::EntityId& entityId)
+    {
+        AZ::Entity* entity = nullptr;
+        AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, AZ::EntityId(entityId));
+    
+        if (entity)
+        {
+            auto& entityComponents = m_entityComponentMap[m_testCase];
+            for (const auto& entityComponent : entity->GetComponents())
+            {
+                const auto componentTypeId = entityComponent->GetUnderlyingComponentType();
+                AZ::ComponentDescriptor* componentDescriptor = nullptr;
+                AZ::ComponentDescriptorBus::EventResult(
+                    componentDescriptor, componentTypeId, &AZ::ComponentDescriptorBus::Events::GetDescriptor);
+                entityComponents[componentTypeId] = componentDescriptor;
+            }
+        }
+    }
+    
+    AZStd::unordered_set<AZStd::string> PythonCoverageEditorSystemComponent::GetParentComponentModulesForAllActivatedEntities(
+        const AZStd::unordered_map<AZ::Uuid, AZ::ComponentDescriptor*>& entityComponents) const
+    {
+        AZStd::unordered_set<AZStd::string> coveringModuleOutputNames;
+        for (const auto& [uuid, componentDescriptor] : entityComponents)
+        {
+            if (const auto moduleComponent = m_moduleComponents.find(uuid); moduleComponent != m_moduleComponents.end())
+            {
+                coveringModuleOutputNames.insert(moduleComponent->second);
+            }
+        }
+    
+        return coveringModuleOutputNames;
+    }
+    
+    void PythonCoverageEditorSystemComponent::OnStartExecuteByFilenameAsTest(AZStd::string_view filename, AZStd::string_view testCase, [[maybe_unused]] const AZStd::vector<AZStd::string_view>& args)
+    {
+        if (m_coverageState == CoverageState::Disabled)
+        {
+            return;
+        }
+
+        if (m_coverageState == CoverageState::Gathering)
+        {
+            // Dump any existing coverage data to disk
+            WriteCoverageFile();
+            m_coverageState = CoverageState::Idle;
+        }
+        
+        if (testCase.empty())
+        {
+            // We need to be able to pinpoint the coverage data to the specific test case names otherwise we will not be able
+            // to specify which specific tests should be run in the future (filename does not necessarily equate to test case name)
+            AZ_Error(LogCallSite, false, "No test case specified, coverage data gathering will be disabled for this test");
+            return;
+        }
+
+        const AZStd::string scriptName = AZ::IO::Path(filename).Stem().Native();
+        const auto coverageFile = m_coverageDir / AZStd::string::format("%s.pycoverage", scriptName.c_str());
+
+        // If this is a different python script we clear the existing entity components and start afresh
+        if (m_coverageFile != coverageFile)
+        {
+            m_entityComponentMap.clear();
+            m_coverageFile = coverageFile;
+        }
+
+        m_testCase = testCase;
+        m_coverageState = CoverageState::Gathering;
+    }
+} // namespace PythonCoverage

+ 88 - 0
AutomatedTesting/Gem/PythonCoverage/Code/Source/PythonCoverageEditorSystemComponent.h

@@ -0,0 +1,88 @@
+/*
+ * 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 <AzCore/Component/Component.h>
+#include <AzCore/Component/EntityBus.h>
+#include <AzCore/IO/Path/Path.h>
+#include <AzCore/std/optional.h>
+#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
+#include <AzToolsFramework/API/EditorPythonScriptNotificationsBus.h>
+
+namespace AZ
+{
+    class ComponentDescriptor;
+}
+
+namespace PythonCoverage
+{
+    //! System component for PythonCoverage editor.
+    class PythonCoverageEditorSystemComponent
+        : public AZ::Component
+        , private AZ::EntitySystemBus::Handler
+        , private AzToolsFramework::EditorPythonScriptNotificationsBus::Handler
+    {
+    public:
+        AZ_COMPONENT(PythonCoverageEditorSystemComponent, "{33370075-3aea-49c4-823d-476f8ac95b6f}");
+        static void Reflect(AZ::ReflectContext* context);
+
+        PythonCoverageEditorSystemComponent() = default;
+
+    private:
+        //! The coverage state for Python tests.
+        enum class CoverageState : AZ::u8
+        {
+            Disabled, //!< Python coverage is disabled.
+            Idle, //!< Python coverage is enabled but not actively gathering coverage data.
+            Gathering //!< Python coverage is enabled and actively gathering coverage data.
+        };
+
+        // AZ::Component overrides...
+        void Activate() override;
+        void Deactivate() override;
+
+        // AZ::EntitySystemBus overrides...
+        void OnEntityActivated(const AZ::EntityId& entityId) override;
+
+        // AZ::EditorPythonScriptNotificationsBus ...
+        void OnStartExecuteByFilenameAsTest(AZStd::string_view filename, AZStd::string_view testCase, const AZStd::vector<AZStd::string_view>& args) override;
+
+        //! Enumerates all of the loaded shared library modules and the component descriptors that belong to them.
+        void EnumerateAllModuleComponents();
+
+        //! Enumerates all of the component descriptors for the specified entity.
+        void EnumerateComponentsForEntity(const AZ::EntityId& entityId);
+
+        //! Attempts to parse the test impact analysis framework configuration file.
+        //! If either the test impact analysis framework is disabled or the configuration file cannot be parsed, python coverage is disabled.
+        //! @returns The coverage state after the parsing attempt.
+        CoverageState ParseCoverageOutputDirectory();
+
+        //! Returns all of the shared library modules that parent the component descriptors of the specified set of activated entities.
+        //! @note Entity component descriptors are still retrieved even if the entity in question has since been deactivated.
+        //! @param entityComponents The set of activated entities and their component descriptors to get the parent modules for.
+        AZStd::unordered_set<AZStd::string> GetParentComponentModulesForAllActivatedEntities(
+            const AZStd::unordered_map<AZ::Uuid, AZ::ComponentDescriptor*>& entityComponents) const;
+
+        //! Writes the current coverage data snapshot to disk.
+        void WriteCoverageFile();
+
+        CoverageState m_coverageState = CoverageState::Disabled; //!< Current coverage state.
+        AZStd::unordered_map<AZStd::string, AZStd::unordered_map<AZ::Uuid, AZ::ComponentDescriptor*>> m_entityComponentMap; //!< Map of
+        //!< component IDs to component descriptors for all activated entities, organized by test cases.
+        AZStd::unordered_map<AZ::Uuid, AZStd::string> m_moduleComponents; //!< Map of component IDs to module names for all modules.
+        AZ::IO::Path m_coverageDir; //!< Directory to write coverage data to.
+        AZ::IO::Path m_coverageFile; //!< Full file path to write coverage data to.
+        AZStd::string m_testCase; //!< Name of current test case that coverage data is being gathered for.
+    };
+} // namespace PythonCoverage

+ 15 - 0
AutomatedTesting/Gem/PythonCoverage/Code/pythoncoverage_editor_files.cmake

@@ -0,0 +1,15 @@
+#
+# 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/PythonCoverageEditorSystemComponent.cpp
+    Source/PythonCoverageEditorSystemComponent.h
+)

+ 15 - 0
AutomatedTesting/Gem/PythonCoverage/Code/pythoncoverage_editor_shared_files.cmake

@@ -0,0 +1,15 @@
+#
+# 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/PythonCoverageEditorModule.cpp
+    Source/PythonCoverageEditorModule.h
+)

+ 13 - 0
AutomatedTesting/Gem/PythonCoverage/gem.json

@@ -0,0 +1,13 @@
+{
+    "gem_name": "PythonCoverage",
+    "display_name": "PythonCoverage",
+    "summary": "A tool for generating gem coverage for Python tests.",
+    "canonical_tags": [
+        "Gem"
+    ],
+    "user_tags": [
+        "PythonCoverage"
+    ],
+    "icon_path": "preview.png",
+    "restricted_name": "gems"
+}

+ 3 - 0
AutomatedTesting/Gem/PythonCoverage/preview.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa
+size 41127

+ 1 - 1
AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py

@@ -49,7 +49,7 @@ def launch_and_validate_results(request, test_directory, editor, editor_script,
     logger.debug("Running automated test: {}".format(editor_script))
     editor.args.extend(["--skipWelcomeScreenDialog", "--regset=/Amazon/Settings/EnableSourceControl=false", 
                         "--regset=/Amazon/Preferences/EnablePrefabSystem=false", run_python, test_case,
-                        "--runpythonargs", " ".join(cfg_args)])
+                        f"--pythontestcase={request.node.originalname}", "--runpythonargs", " ".join(cfg_args)])
     if auto_test_mode:
         editor.args.extend(["--autotest_mode"])
     if null_renderer:

+ 1 - 1
AutomatedTesting/Gem/PythonTests/automatedtesting_shared/base.py

@@ -89,7 +89,7 @@ class TestAutomationBase:
         editor_starttime = time.time()
         self.logger.debug("Running automated test")
         testcase_module_filepath = self._get_testcase_module_filepath(testcase_module)
-        pycmd = ["--runpythontest", testcase_module_filepath, "-BatchMode", "-autotest_mode", "-rhi=null"] + extra_cmdline_args
+        pycmd = ["--runpythontest", testcase_module_filepath, "-BatchMode", "-autotest_mode", "-rhi=null", f"-pythontestcase={request.node.originalname}"] + extra_cmdline_args
         editor.args.extend(pycmd) # args are added to the WinLauncher start command
         editor.start(backupFiles = False, launch_ap = False)
         try:

+ 10 - 4
Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonRunnerRequestsBus.h

@@ -24,16 +24,22 @@ namespace AzToolsFramework
         //////////////////////////////////////////////////////////////////////////
 
         //! executes a Python script using a string, prints the result if printResult is true and script is an expression
-        virtual void ExecuteByString(AZStd::string_view script, bool printResult) { AZ_UNUSED(script); AZ_UNUSED(printResult); }
+        virtual void ExecuteByString([[maybe_unused]] AZStd::string_view script, [[maybe_unused]] bool printResult) {}
 
         //! executes a Python script using a filename
-        virtual void ExecuteByFilename(AZStd::string_view filename) { AZ_UNUSED(filename); }
+        virtual void ExecuteByFilename([[maybe_unused]] AZStd::string_view filename) {}
 
         //! executes a Python script using a filename and args
-        virtual void ExecuteByFilenameWithArgs(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args) { AZ_UNUSED(filename); AZ_UNUSED(args);  }
+        virtual void ExecuteByFilenameWithArgs(
+            [[maybe_unused]] AZStd::string_view filename, [[maybe_unused]] const AZStd::vector<AZStd::string_view>& args) {}
 
         //! executes a Python script as a test
-        virtual void ExecuteByFilenameAsTest(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args) { AZ_UNUSED(filename); AZ_UNUSED(args); }
+        virtual void ExecuteByFilenameAsTest(
+            [[maybe_unused]] AZStd::string_view filename,
+            [[maybe_unused]] AZStd::string_view testCase,
+            [[maybe_unused]] const AZStd::vector<AZStd::string_view>& args)
+        {
+        }
     };
     using EditorPythonRunnerRequestBus = AZ::EBus<EditorPythonRunnerRequests>;
 

+ 45 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonScriptNotificationsBus.h

@@ -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.
+ *
+ */
+#pragma once
+
+#include <AzCore/EBus/EBus.h>
+#include <AzCore/std/string/string.h>
+
+namespace AzToolsFramework
+{
+    //! Provides a bus to notify when Python scripts are about to run.
+    class EditorPythonScriptNotifications
+        : public AZ::EBusTraits
+    {
+    public:
+        //////////////////////////////////////////////////////////////////////////
+        // EBusTraits overrides
+        static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
+        static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
+        //////////////////////////////////////////////////////////////////////////
+
+        //! Notifies the start of execution of a Python script using a string.
+        virtual void OnStartExecuteByString([[maybe_unused]] AZStd::string_view script) {}
+
+        //! Notifies the start of execution of a Python script using a filename.
+        virtual void OnStartExecuteByFilename([[maybe_unused]] AZStd::string_view filename) {}
+
+        //! Notifies the start of execution of a Python script using a filename and args.
+        virtual void OnStartExecuteByFilenameWithArgs(
+            [[maybe_unused]] AZStd::string_view filename, [[maybe_unused]] const AZStd::vector<AZStd::string_view>& args) {}
+
+        //! Notifies the start of execution of a Python script as a test.
+        virtual void OnStartExecuteByFilenameAsTest(
+            [[maybe_unused]] AZStd::string_view filename, [[maybe_unused]] AZStd::string_view testCase, [[maybe_unused]] const AZStd::vector<AZStd::string_view>& args) {}
+    };
+    using EditorPythonScriptNotificationsBus = AZ::EBus<EditorPythonScriptNotifications>;
+}

+ 12 - 3
Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTreeView.cpp

@@ -180,10 +180,11 @@ namespace AzToolsFramework
 
         void AssetBrowserTreeView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
         {
-            // if selected entry is being removed, clear selection so not to select (and attempt to preview) other entries potentially marked for deletion
-            if (selectionModel() && selectionModel()->selectedIndexes().size() == 1)
+            // if selected entry is being removed, clear selection so not to select (and attempt to preview) other entries potentially
+            // marked for deletion
+            if (selectionModel() && selectedIndexes().size() == 1)
             {
-                QModelIndex selectedIndex = selectionModel()->selectedIndexes().first();
+                QModelIndex selectedIndex = selectedIndexes().first();
                 QModelIndex parentSelectedIndex = selectedIndex.parent();
                 if (parentSelectedIndex == parent && selectedIndex.row() >= start && selectedIndex.row() <= end)
                 {
@@ -193,6 +194,14 @@ namespace AzToolsFramework
             QTreeView::rowsAboutToBeRemoved(parent, start, end);
         }
 
+        // Item data for hidden columns normally isn't copied by Qt during drag-and-drop (see QTBUG-30242).
+        // However, for the AssetBrowser, the hidden columns should get copied. By overriding selectedIndexes() to
+        // include all selected indices, not just the visible ones, we can get the behavior we're looking for.
+        QModelIndexList AssetBrowserTreeView::selectedIndexes() const
+        {
+            return selectionModel()->selectedIndexes();
+        }
+
         void AssetBrowserTreeView::SetThumbnailContext(const char* thumbnailContext) const
         {
             m_delegate->SetThumbnailContext(thumbnailContext);

+ 3 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTreeView.h

@@ -90,6 +90,9 @@ namespace AzToolsFramework
             void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) override;
             void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) override;
 
+        protected:
+            QModelIndexList selectedIndexes() const override;
+
         private:
             QPointer<AssetBrowserModel> m_assetBrowserModel = nullptr;
             QPointer<AssetBrowserFilterModel> m_assetBrowserSortFilterProxyModel = nullptr;

+ 1 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake

@@ -37,6 +37,7 @@ set(FILES
     API/EditorVegetationRequestsBus.h
     API/EditorPythonConsoleBus.h
     API/EditorPythonRunnerRequestsBus.h
+    API/EditorPythonScriptNotificationsBus.h
     API/EntityPropertyEditorRequestsBus.h
     API/EditorWindowRequestBus.h
     API/EntityCompositionRequestBus.h

+ 9 - 1
Code/Sandbox/Editor/CryEdit.cpp

@@ -532,6 +532,7 @@ public:
     QString m_appRoot;
     QString m_logFile;
     QString m_pythonArgs;
+    QString m_pythontTestCase;
     QString m_execFile;
     QString m_execLineCmd;
 
@@ -578,6 +579,7 @@ public:
         const std::vector<std::pair<CommandLineStringOption, QString&> > stringOptions = {
             {{"logfile", "File name of the log file to write out to.", "logfile"}, m_logFile},
             {{"runpythonargs", "Command-line argument string to pass to the python script if --runpython or --runpythontest was used.", "runpythonargs"}, m_pythonArgs},
+            {{"pythontestcase", "Test case name of python test script if --runpythontest was used.", "pythontestcase"}, m_pythontTestCase},
             {{"exec", "cfg file to run on startup, used for systems like automation", "exec"}, m_execFile},
             {{"rhi", "Command-line argument to force which rhi to use", "dummyString"}, dummyString },
             {{"rhi-device-validation", "Command-line argument to configure rhi validation", "dummyString"}, dummyString },
@@ -1516,7 +1518,13 @@ void CCryEditApp::RunInitPythonScript(CEditCommandLineInfo& cmdInfo)
             std::transform(tokens.begin(), tokens.end(), std::back_inserter(pythonArgs), [](auto& tokenData) { return tokenData.c_str(); });
             if (cmdInfo.m_bRunPythonTestScript)
             {
-                EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilenameAsTest, cmdInfo.m_strFileName.toUtf8().constData(), pythonArgs);
+                AZStd::string pythonTestCase;
+                if (!cmdInfo.m_pythontTestCase.isEmpty())
+                {
+                    pythonTestCase = cmdInfo.m_pythontTestCase.toUtf8().constData();
+                }
+
+                EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilenameAsTest, cmdInfo.m_strFileName.toUtf8().constData(), pythonTestCase, pythonArgs);
 
                 // Close the editor gracefully as the test has completed
                 GetIEditor()->GetDocument()->SetModifiedFlag(false);

+ 3 - 0
Code/Tools/TestImpactFramework/Frontend/Console/Code/CMakeLists.txt

@@ -15,6 +15,9 @@ ly_add_target(
             Include
         PRIVATE
             Source
+    COMPILE_DEFINITIONS
+        PRIVATE
+            ${LY_TEST_IMPACT_CONFIG_FILE_PATH_DEFINITION}
     BUILD_DEPENDENCIES
         PUBLIC
             AZ::TestImpact.Runtime.Static

+ 41 - 28
Gems/Atom/RHI/Vulkan/Code/Source/RHI/Device.cpp

@@ -61,30 +61,7 @@ namespace AZ
             RawStringList requiredLayers = GetRequiredLayers();
             RawStringList requiredExtensions = GetRequiredExtensions();
 
-            StringList deviceExtensions = physicalDevice.GetDeviceExtensionNames();
-            RawStringList optionalDeviceExtensions = { {
-                VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME,
-                VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME,
-                VK_EXT_MEMORY_BUDGET_EXTENSION_NAME,
-                VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME,
-                VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME,
-                VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME,
-                VK_KHR_RELAXED_BLOCK_LAYOUT_EXTENSION_NAME,
-                VK_EXT_MEMORY_BUDGET_EXTENSION_NAME,
-                VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
-                VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME,
-
-                // ray tracing extensions
-                VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME,
-                VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME,
-                VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME,
-                VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME,
-                VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME,
-                VK_KHR_SPIRV_1_4_EXTENSION_NAME,
-                VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME
-            } };
-
-            RawStringList optionalExtensions = FilterList(optionalDeviceExtensions, deviceExtensions);
+            RawStringList optionalExtensions = physicalDevice.FilterSupportedOptionalExtensions();
             requiredExtensions.insert(requiredExtensions.end(), optionalExtensions.begin(), optionalExtensions.end());
 
             //We now need to find the queues that the physical device has available and make sure 
@@ -159,7 +136,7 @@ namespace AZ
 
             // unbounded array functionality
             VkPhysicalDeviceDescriptorIndexingFeaturesEXT descriptorIndexingFeatures = {};
-            descriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES;
+            descriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT;
             const VkPhysicalDeviceDescriptorIndexingFeaturesEXT& physicalDeviceDescriptorIndexingFeatures =
                 physicalDevice.GetPhysicalDeviceDescriptorIndexingFeatures();
             descriptorIndexingFeatures.shaderInputAttachmentArrayDynamicIndexing = physicalDeviceDescriptorIndexingFeatures.shaderInputAttachmentArrayDynamicIndexing;
@@ -176,6 +153,15 @@ namespace AZ
             descriptorIndexingFeatures.descriptorBindingVariableDescriptorCount = physicalDeviceDescriptorIndexingFeatures.descriptorBindingVariableDescriptorCount;
             descriptorIndexingFeatures.runtimeDescriptorArray = physicalDeviceDescriptorIndexingFeatures.runtimeDescriptorArray;
 
+            VkPhysicalDeviceBufferDeviceAddressFeaturesEXT bufferDeviceAddressFeatures = {};
+            bufferDeviceAddressFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT;
+            const VkPhysicalDeviceBufferDeviceAddressFeaturesEXT& physicalDeviceBufferDeviceAddressFeatures =
+                physicalDevice.GetPhysicalDeviceBufferDeviceAddressFeatures();
+            bufferDeviceAddressFeatures.bufferDeviceAddress = physicalDeviceBufferDeviceAddressFeatures.bufferDeviceAddress;
+            bufferDeviceAddressFeatures.bufferDeviceAddressCaptureReplay = physicalDeviceBufferDeviceAddressFeatures.bufferDeviceAddressCaptureReplay;
+            bufferDeviceAddressFeatures.bufferDeviceAddressMultiDevice = physicalDeviceBufferDeviceAddressFeatures.bufferDeviceAddressMultiDevice;
+            descriptorIndexingFeatures.pNext = &bufferDeviceAddressFeatures;
+
             VkPhysicalDeviceDepthClipEnableFeaturesEXT depthClipEnabled = {};
             depthClipEnabled.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT;
             depthClipEnabled.depthClipEnable = physicalDevice.GetPhysicalDeviceDepthClipEnableFeatures().depthClipEnable;
@@ -184,7 +170,7 @@ namespace AZ
             VkPhysicalDeviceRobustness2FeaturesEXT robustness2 = {};
             robustness2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT;
             robustness2.nullDescriptor = physicalDevice.GetPhysicalDeviceRobutness2Features().nullDescriptor;
-            depthClipEnabled.pNext = &robustness2;
+            bufferDeviceAddressFeatures.pNext = &robustness2;
 
             VkPhysicalDeviceVulkan12Features vulkan12Features = {};
             VkPhysicalDeviceShaderFloat16Int8FeaturesKHR float16Int8 = {};
@@ -195,6 +181,7 @@ namespace AZ
             if (majorVersion >= 1 && minorVersion >= 2)
             {
                 vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
+                VkPhysicalDeviceVulkan12Features physicalDeviceVulkan12Features = physicalDevice.GetPhysicalDeviceVulkan12Features();
                 vulkan12Features.drawIndirectCount = physicalDevice.GetPhysicalDeviceVulkan12Features().drawIndirectCount;
                 vulkan12Features.shaderFloat16 = physicalDevice.GetPhysicalDeviceVulkan12Features().shaderFloat16;
                 vulkan12Features.shaderInt8 = physicalDevice.GetPhysicalDeviceVulkan12Features().shaderInt8;
@@ -795,6 +782,27 @@ namespace AZ
             return *m_nullDescriptorManager;
         }
 
+        VkBufferUsageFlags Device::GetBufferUsageFlagBitsUnderRestrictions(RHI::BufferBindFlags bindFlags) const
+        {
+            VkBufferUsageFlags bufferUsageFlags = GetBufferUsageFlagBits(bindFlags);
+
+            const auto& physicalDevice = static_cast<const PhysicalDevice&>(GetPhysicalDevice());
+
+            // VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT require bufferDeviceAddress enabled.
+            if (!physicalDevice.GetPhysicalDeviceBufferDeviceAddressFeatures().bufferDeviceAddress)
+            {
+                bufferUsageFlags &= ~VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
+            }
+            // VK_KHR_acceleration_structure provides VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR
+            // Otherwise unrecognized flag.
+            if (!physicalDevice.IsOptionalDeviceExtensionSupported(OptionalDeviceExtension::AccelerationStructure))
+            {
+                bufferUsageFlags &= ~VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR;
+            }
+
+            return bufferUsageFlags;
+        }
+
         VkBuffer Device::CreateBufferResouce(const RHI::BufferDescriptor& descriptor) const
         {
             AZ_Assert(descriptor.m_sharedQueueMask != RHI::HardwareQueueClassMask::None, "Invalid shared queue mask");
@@ -805,9 +813,14 @@ namespace AZ
             createInfo.pNext = nullptr;
             createInfo.flags = 0;
             createInfo.size = descriptor.m_byteCount;
-            createInfo.usage = GetBufferUsageFlagBits(descriptor.m_bindFlags);
+            createInfo.usage = GetBufferUsageFlagBitsUnderRestrictions(descriptor.m_bindFlags);
             // Trying to guess here if the buffers are going to be used as attachments. Maybe it would be better to add an explicit flag in the descriptor.
-            createInfo.sharingMode = RHI::CheckBitsAny(descriptor.m_bindFlags, RHI::BufferBindFlags::ShaderWrite | RHI::BufferBindFlags::Predication | RHI::BufferBindFlags::Indirect) ? VK_SHARING_MODE_EXCLUSIVE : VK_SHARING_MODE_CONCURRENT;
+            createInfo.sharingMode =
+                RHI::CheckBitsAny(
+                    descriptor.m_bindFlags,
+                    RHI::BufferBindFlags::ShaderWrite | RHI::BufferBindFlags::Predication | RHI::BufferBindFlags::Indirect)
+                ? VK_SHARING_MODE_EXCLUSIVE
+                : VK_SHARING_MODE_CONCURRENT;
             createInfo.queueFamilyIndexCount = static_cast<uint32_t>(queueFamilies.size());
             createInfo.pQueueFamilyIndices = queueFamilies.empty() ? nullptr : queueFamilies.data();
 

+ 4 - 0
Gems/Atom/RHI/Vulkan/Code/Source/RHI/Device.h

@@ -144,6 +144,10 @@ namespace AZ
             template <typename ObjectType, typename... Args>
             RHI::Ptr<ObjectType> AcquireObjectFromCache(ObjectCache<ObjectType>& cache, const size_t hash, Args... args);
 
+            //! Get the vulkan buffer usage flags from buffer bind flags.
+            //! Flags will be corrected if required features or extensions are not enabled.
+            VkBufferUsageFlags GetBufferUsageFlagBitsUnderRestrictions(RHI::BufferBindFlags bindFlags) const;
+
             VkDevice m_nativeDevice = VK_NULL_HANDLE;
             VkPhysicalDeviceFeatures m_enabledDeviceFeatures{};
             VkPipelineStageFlags m_supportedPipelineStageFlagsMask = ~0;

+ 67 - 1
Gems/Atom/RHI/Vulkan/Code/Source/RHI/PhysicalDevice.cpp

@@ -128,6 +128,11 @@ namespace AZ
             return m_descriptorIndexingFeatures;
         }
 
+        const VkPhysicalDeviceBufferDeviceAddressFeaturesEXT& PhysicalDevice::GetPhysicalDeviceBufferDeviceAddressFeatures() const
+        {
+            return m_bufferDeviceAddressFeatures;
+        }
+
         const VkPhysicalDeviceVulkan12Features& PhysicalDevice::GetPhysicalDeviceVulkan12Features() const
         {
             return m_vulkan12Features;
@@ -229,6 +234,56 @@ namespace AZ
                 (m_separateDepthStencilFeatures.separateDepthStencilLayouts && VK_DEVICE_EXTENSION_SUPPORTED(KHR_separate_depth_stencil_layouts)) ||
                 (m_vulkan12Features.separateDepthStencilLayouts));
             m_features.set(static_cast<size_t>(DeviceFeature::DescriptorIndexing), VK_DEVICE_EXTENSION_SUPPORTED(EXT_descriptor_indexing));
+            m_features.set(static_cast<size_t>(DeviceFeature::BufferDeviceAddress), VK_DEVICE_EXTENSION_SUPPORTED(EXT_buffer_device_address));
+        }
+
+        RawStringList PhysicalDevice::FilterSupportedOptionalExtensions()
+        {
+            // The order must match the enum OptionalDeviceExtensions
+            RawStringList optionalExtensions = { {
+                VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME,
+                VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME,
+                VK_EXT_MEMORY_BUDGET_EXTENSION_NAME,
+                VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME,
+                VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME,
+                VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME,
+                VK_KHR_RELAXED_BLOCK_LAYOUT_EXTENSION_NAME,
+                VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
+                VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME,
+
+                // ray tracing extensions
+                VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME,
+                VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME,
+                VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME,
+                VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME,
+                VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME,
+                VK_KHR_SPIRV_1_4_EXTENSION_NAME,
+                VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME
+            } };
+
+            uint32_t optionalExtensionCount = sizeof(optionalExtensions) / sizeof(VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME);
+
+            AZ_Assert(optionalExtensionCount == static_cast<uint32_t>(OptionalDeviceExtension::Count), "The order and size must match the enum OptionalDeviceExtensions.");
+
+            // Optional device extensions are filtered based on what the device support.
+            // It returns in the same order as in the original list.
+            StringList deviceExtensions = GetDeviceExtensionNames();
+            RawStringList filteredOptionalExtensions = FilterList(optionalExtensions, deviceExtensions);
+
+            // Mark the supported optional extensions in the bitset for faster look up compared to string search.
+            uint32_t originalIndex = 0;
+            for (const auto& extension : filteredOptionalExtensions)
+            {
+                AZ_Assert(originalIndex < optionalExtensionCount, "Out of range index. Check FilterList algorithm if list is returned in the original order.");
+                while (strcmp(extension, optionalExtensions[originalIndex]) != 0)
+                {
+                    ++originalIndex;
+                }
+                m_optionalExtensions.set(originalIndex);
+                ++originalIndex;
+            }
+
+            return filteredOptionalExtensions;
         }
 
         void PhysicalDevice::CompileMemoryStatistics(RHI::MemoryStatisticsBuilder& builder) const
@@ -266,10 +321,15 @@ namespace AZ
                 descriptorIndexingFeatures = {};
                 descriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT;
 
+                VkPhysicalDeviceBufferDeviceAddressFeaturesEXT& bufferDeviceAddressFeatures = m_bufferDeviceAddressFeatures;
+                bufferDeviceAddressFeatures = {};
+                bufferDeviceAddressFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT;
+                descriptorIndexingFeatures.pNext = &bufferDeviceAddressFeatures;
+
                 VkPhysicalDeviceDepthClipEnableFeaturesEXT& dephClipEnableFeatures = m_dephClipEnableFeatures;
                 dephClipEnableFeatures = {};
                 dephClipEnableFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT;
-                descriptorIndexingFeatures.pNext = &dephClipEnableFeatures;
+                bufferDeviceAddressFeatures.pNext = &dephClipEnableFeatures;
 
                 VkPhysicalDeviceRobustness2FeaturesEXT& robustness2Feature = m_robutness2Features;
                 robustness2Feature = {};
@@ -397,5 +457,11 @@ namespace AZ
             return m_features.test(index);
         }
 
+        bool PhysicalDevice::IsOptionalDeviceExtensionSupported(OptionalDeviceExtension optionalDeviceExtension) const
+        {
+            uint32_t index = static_cast<uint32_t>(optionalDeviceExtension);
+            AZ_Assert(index < m_optionalExtensions.size(), "Invalid feature %d", index);
+            return m_optionalExtensions.test(index);
+        }
     }
 }

+ 28 - 0
Gems/Atom/RHI/Vulkan/Code/Source/RHI/PhysicalDevice.h

@@ -30,9 +30,31 @@ namespace AZ
             NullDescriptor,
             SeparateDepthStencil,
             DescriptorIndexing,
+            BufferDeviceAddress,
             Count // Must be last
         };
 
+        enum class OptionalDeviceExtension : uint32_t
+        {
+            SampleLocation = 0,
+            ConditionalRendering,
+            MemoryBudget,
+            DepthClipEnable,
+            ConservativeRasterization,
+            DrawIndirectCount,
+            RelaxedBlockLayout,
+            Robustness2,
+            ShaderFloat16Int8,
+            AccelerationStructure,
+            RayTracingPipeline,
+            BufferDeviceAddress,
+            DeferredHostOperations,
+            DescriptorIndexing,
+            Spirv14,
+            ShaderFloatControls,
+            Count
+        };
+
         class PhysicalDevice final
             : public RHI::PhysicalDevice
         {
@@ -46,6 +68,7 @@ namespace AZ
             const VkPhysicalDevice& GetNativePhysicalDevice() const;
             const VkPhysicalDeviceMemoryProperties& GetMemoryProperties() const;
             bool IsFeatureSupported(DeviceFeature feature) const;
+            bool IsOptionalDeviceExtensionSupported(OptionalDeviceExtension optionalDeviceExtension) const;
             const VkPhysicalDeviceLimits& GetDeviceLimits() const;
             const VkPhysicalDeviceFeatures& GetPhysicalDeviceFeatures() const;
             const VkPhysicalDeviceProperties& GetPhysicalDeviceProperties() const;
@@ -54,6 +77,7 @@ namespace AZ
             const VkPhysicalDeviceRobustness2FeaturesEXT& GetPhysicalDeviceRobutness2Features() const;
             const VkPhysicalDeviceShaderFloat16Int8FeaturesKHR& GetPhysicalDeviceFloat16Int8Features() const;
             const VkPhysicalDeviceDescriptorIndexingFeaturesEXT& GetPhysicalDeviceDescriptorIndexingFeatures() const;
+            const VkPhysicalDeviceBufferDeviceAddressFeaturesEXT& GetPhysicalDeviceBufferDeviceAddressFeatures() const;
             const VkPhysicalDeviceVulkan12Features& GetPhysicalDeviceVulkan12Features() const;
             const VkPhysicalDeviceSeparateDepthStencilLayoutsFeaturesKHR& GetPhysicalDeviceSeparateDepthStencilFeatures() const;
             const VkPhysicalDeviceAccelerationStructurePropertiesKHR& GetPhysicalDeviceAccelerationStructureProperties() const;
@@ -63,6 +87,8 @@ namespace AZ
             StringList GetDeviceExtensionNames(const char* layerName = nullptr) const;
             bool IsFormatSupported(RHI::Format format, VkImageTiling tiling, VkFormatFeatureFlags features) const;
             void LoadSupportedFeatures();
+            //! Filter optional extensions based on what the physics device support.
+            RawStringList FilterSupportedOptionalExtensions();
             void CompileMemoryStatistics(RHI::MemoryStatisticsBuilder& builder) const;
 
         private:
@@ -77,6 +103,7 @@ namespace AZ
             VkPhysicalDevice m_vkPhysicalDevice = VK_NULL_HANDLE;
             VkPhysicalDeviceMemoryProperties m_memoryProperty{};
 
+            AZStd::bitset<static_cast<uint32_t>(OptionalDeviceExtension::Count)> m_optionalExtensions;
             AZStd::bitset<static_cast<uint32_t>(DeviceFeature::Count)> m_features;
             VkPhysicalDeviceFeatures m_deviceFeatures{};
             VkPhysicalDeviceProperties m_deviceProperties{};
@@ -85,6 +112,7 @@ namespace AZ
             VkPhysicalDeviceRobustness2FeaturesEXT m_robutness2Features{};
             VkPhysicalDeviceShaderFloat16Int8FeaturesKHR m_float16Int8Features{};
             VkPhysicalDeviceDescriptorIndexingFeaturesEXT m_descriptorIndexingFeatures{};
+            VkPhysicalDeviceBufferDeviceAddressFeaturesEXT m_bufferDeviceAddressFeatures{};
             VkPhysicalDeviceSeparateDepthStencilLayoutsFeaturesKHR m_separateDepthStencilFeatures{};
             VkPhysicalDeviceAccelerationStructurePropertiesKHR m_accelerationStructureProperties{};
             VkPhysicalDeviceRayTracingPipelinePropertiesKHR m_rayTracingPipelineProperties{};

+ 11 - 1
Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp

@@ -34,6 +34,7 @@
 #include <AzFramework/StringFunc/StringFunc.h>
 
 #include <AzToolsFramework/API/EditorPythonConsoleBus.h>
+#include <AzToolsFramework/API/EditorPythonScriptNotificationsBus.h>
 
 namespace Platform
 {
@@ -575,6 +576,9 @@ namespace EditorPythonBindings
 
         if (!script.empty())
         {
+            AzToolsFramework::EditorPythonScriptNotificationsBus::Broadcast(
+                &AzToolsFramework::EditorPythonScriptNotificationsBus::Events::OnStartExecuteByString, script);
+
             // Acquire GIL before calling Python code
             AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
             pybind11::gil_scoped_acquire acquire;
@@ -635,11 +639,15 @@ namespace EditorPythonBindings
     void PythonSystemComponent::ExecuteByFilename(AZStd::string_view filename)
     {
         AZStd::vector<AZStd::string_view> args;
+        AzToolsFramework::EditorPythonScriptNotificationsBus::Broadcast(
+            &AzToolsFramework::EditorPythonScriptNotificationsBus::Events::OnStartExecuteByFilename, filename);
         ExecuteByFilenameWithArgs(filename, args);
     }
 
-    void PythonSystemComponent::ExecuteByFilenameAsTest(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args)
+    void PythonSystemComponent::ExecuteByFilenameAsTest(AZStd::string_view filename, AZStd::string_view testCase, const AZStd::vector<AZStd::string_view>& args)
     {
+        AzToolsFramework::EditorPythonScriptNotificationsBus::Broadcast(
+            &AzToolsFramework::EditorPythonScriptNotificationsBus::Events::OnStartExecuteByFilenameAsTest, filename, testCase, args);
         const Result evalResult = EvaluateFile(filename, args);
         if (evalResult == Result::Okay)
         {
@@ -655,6 +663,8 @@ namespace EditorPythonBindings
 
     void PythonSystemComponent::ExecuteByFilenameWithArgs(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args)
     {
+        AzToolsFramework::EditorPythonScriptNotificationsBus::Broadcast(
+            &AzToolsFramework::EditorPythonScriptNotificationsBus::Events::OnStartExecuteByFilenameWithArgs, filename, args);
         EvaluateFile(filename, args);
     }
 

+ 1 - 1
Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.h

@@ -53,7 +53,7 @@ namespace EditorPythonBindings
         void ExecuteByString(AZStd::string_view script, bool printResult) override;
         void ExecuteByFilename(AZStd::string_view filename) override;
         void ExecuteByFilenameWithArgs(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args) override;
-        void ExecuteByFilenameAsTest(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args) override;
+        void ExecuteByFilenameAsTest(AZStd::string_view filename, AZStd::string_view testCase, const AZStd::vector<AZStd::string_view>& args) override;
         ////////////////////////////////////////////////////////////////////////
         
     private:

+ 3 - 116
cmake/TestImpactFramework/ConsoleFrontendConfig.in

@@ -70,6 +70,9 @@
       },
       "test_target_meta": {
         "file": "${test_target_type_file}"
+      },
+      "gem_target": {
+        "file": "${gem_target_file}"
       }
     }
   },
@@ -87,122 +90,6 @@
      
     ],
     "shard": [
-      {
-        "policy": "fixture_contiguous",
-        "target": "AzCore.Tests"
-      },
-      {
-        "policy": "fixture_contiguous",
-        "target": "AzToolsFramework.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "Framework.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "LmbrCentral.Editor.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "EditorLib.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "PhysX.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "ImageProcessing.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "Atom_RPI.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "Atom_RHI.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "AzManipulatorFramework.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "WhiteBox.Editor.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "ImageProcessing.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "AzManipulatorTestFramework.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "AtomCore.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "ImageProcessingAtom.Editor.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "EditorPythonBindings.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "Atom_Utils.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "AudioEngineWwise.Editor.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "Multiplayer.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "LmbrCentral.Tests"
-      },
-      {
-        "policy": "fixture_contiguous",
-        "target": "LyMetricsShared.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "PhysX.Editor.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "ComponentEntityEditorPlugin.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "DeltaCataloger.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "GradientSignal.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "LyShine.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "EMotionFX.Editor.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "EMotionFX.Tests"
-      },
-      {
-        "policy": "test_interleaved",
-        "target": "CrySystem.Tests"
-      }
     ]
   }
 }

+ 7 - 0
cmake/TestImpactFramework/EnumeratedGemTargets.in

@@ -0,0 +1,7 @@
+{
+  "gems": {
+    [
+${enumerated_gem_targets}
+    ]
+  }
+}

+ 60 - 15
cmake/TestImpactFramework/LYTestImpactFramework.cmake

@@ -14,15 +14,21 @@ option(LY_TEST_IMPACT_INSTRUMENTATION_BIN "Path to test impact framework instrum
 # Name of test impact framework console static library target
 set(LY_TEST_IMPACT_CONSOLE_STATIC_TARGET "TestImpact.Frontend.Console.Static")
 
+# Name of test impact framework python coverage gem target
+set(LY_TEST_IMPACT_PYTHON_COVERAGE_STATIC_TARGET "PythonCoverage.Editor.Static")
+
 # Name of test impact framework console target
 set(LY_TEST_IMPACT_CONSOLE_TARGET "TestImpact.Frontend.Console")
 
-# Directory for non-persistent test impact data trashed with each generation of build system
+# Directory for test impact artifacts and data
 set(LY_TEST_IMPACT_WORKING_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/TestImpactFramework")
 
-# Directory for temporary files generated at runtime
+# Directory for artifacts generated at runtime
 set(LY_TEST_IMPACT_TEMP_DIR "${LY_TEST_IMPACT_WORKING_DIR}/Temp")
 
+# Directory for files that persist between runtime runs
+set(LY_TEST_IMPACT_PERSISTENT_DIR "${LY_TEST_IMPACT_WORKING_DIR}/Persistent")
+
 # Directory for static artifacts produced as part of the build system generation process
 set(LY_TEST_IMPACT_ARTIFACT_DIR "${LY_TEST_IMPACT_WORKING_DIR}/Artifact")
 
@@ -32,9 +38,18 @@ set(LY_TEST_IMPACT_SOURCE_TARGET_MAPPING_DIR "${LY_TEST_IMPACT_ARTIFACT_DIR}/Map
 # Directory for build target dependency/depender graphs
 set(LY_TEST_IMPACT_TARGET_DEPENDENCY_DIR "${LY_TEST_IMPACT_ARTIFACT_DIR}/Dependency")
 
-# Master test enumeration file for all test types
+# Main test enumeration file for all test types
 set(LY_TEST_IMPACT_TEST_TYPE_FILE "${LY_TEST_IMPACT_ARTIFACT_DIR}/TestType/All.tests")
 
+# Main gem target file for all shared library gems
+set(LY_TEST_IMPACT_GEM_TARGET_FILE "${LY_TEST_IMPACT_ARTIFACT_DIR}/BuildType/All.gems")
+
+# Path to the config file for each build configuration
+set(LY_TEST_IMPACT_CONFIG_FILE_PATH "${LY_TEST_IMPACT_PERSISTENT_DIR}/tiaf.$<CONFIG>.json")
+
+# Preprocessor directive for the config file path
+set(LY_TEST_IMPACT_CONFIG_FILE_PATH_DEFINITION "LY_TEST_IMPACT_DEFAULT_CONFIG_FILE=\"${LY_TEST_IMPACT_CONFIG_FILE_PATH}\"")
+
 #! ly_test_impact_rebase_file_to_repo_root: rebases the relative and/or absolute path to be relative to repo root directory and places the resulting path in quotes.
 #
 # \arg:INPUT_FILE the file to rebase
@@ -208,7 +223,7 @@ function(ly_test_impact_extract_python_test_params COMPOSITE_TEST COMPOSITE_SUIT
     set(${TEST_SUITES} ${test_suites} PARENT_SCOPE)
 endfunction()
 
-#! ly_test_impact_write_test_enumeration_file: exports the master test lists to file.
+#! ly_test_impact_write_test_enumeration_file: exports the main test list to file.
 # 
 # \arg:TEST_ENUMERATION_TEMPLATE_FILE path to test enumeration template file
 function(ly_test_impact_write_test_enumeration_file TEST_ENUMERATION_TEMPLATE_FILE)
@@ -258,6 +273,33 @@ function(ly_test_impact_write_test_enumeration_file TEST_ENUMERATION_TEMPLATE_FI
     configure_file(${TEST_ENUMERATION_TEMPLATE_FILE} ${LY_TEST_IMPACT_TEST_TYPE_FILE})
 endfunction()
 
+#! ly_test_impact_write_gem_target_enumeration_file: exports the main gem target list to file.
+#
+# \arg:GEM_TARGET_TEMPLATE_FILE path to source to gem target template file
+function(ly_test_impact_write_gem_target_enumeration_file GEM_TARGET_TEMPLATE_FILE)
+    get_property(LY_ALL_TARGETS GLOBAL PROPERTY LY_ALL_TARGETS)
+
+    set(enumerated_gem_targets "")
+    # Walk the build targets
+    foreach(aliased_target ${LY_ALL_TARGETS})
+
+        unset(target)
+        ly_de_alias_target(${aliased_target} target)
+
+        get_target_property(gem_module ${target} GEM_MODULE)
+        get_target_property(target_type ${target} TYPE)
+        if("${gem_module}" STREQUAL "TRUE")
+            if("${target_type}" STREQUAL "SHARED_LIBRARY" OR "${target_type}" STREQUAL "MODULE_LIBRARY")
+                list(APPEND enumerated_gem_targets "      \"${target}\"")
+            endif()
+        endif()
+    endforeach()
+    string (REPLACE ";" ",\n" enumerated_gem_targets "${enumerated_gem_targets}")
+     # Write out source to target mapping file
+     set(mapping_path "${LY_TEST_IMPACT_GEM_TARGET_FILE}")
+     configure_file(${GEM_TARGET_TEMPLATE_FILE} ${mapping_path})
+endfunction()
+
 #! ly_test_impact_export_source_target_mappings: exports the static source to target mappings to file.
 #
 # \arg:MAPPING_TEMPLATE_FILE path to source to target template file
@@ -269,6 +311,7 @@ function(ly_test_impact_export_source_target_mappings MAPPING_TEMPLATE_FILE)
 
         unset(target)
         ly_de_alias_target(${aliased_target} target)
+
         message(TRACE "Exporting static source file mappings for ${target}")
         
         # Target name and path relative to root
@@ -329,9 +372,8 @@ endfunction()
 #! ly_test_impact_write_config_file: writes out the test impact framework config file using the data derived from the build generation process.
 # 
 # \arg:CONFIG_TEMPLATE_FILE path to the runtime configuration template file
-# \arg:PERSISTENT_DATA_DIR path to the test impact framework persistent data directory
 # \arg:BIN_DIR path to repo binary output directory
-function(ly_test_impact_write_config_file CONFIG_TEMPLATE_FILE PERSISTENT_DATA_DIR BIN_DIR)
+function(ly_test_impact_write_config_file CONFIG_TEMPLATE_FILE BIN_DIR)
     # Platform this config file is being generated for
     set(platform ${PAL_PLATFORM_NAME})
 
@@ -362,16 +404,19 @@ function(ly_test_impact_write_config_file CONFIG_TEMPLATE_FILE PERSISTENT_DATA_D
     set(temp_dir "${LY_TEST_IMPACT_TEMP_DIR}")
 
     # Active persistent data dir
-    set(active_dir "${PERSISTENT_DATA_DIR}/active")
+    set(active_dir "${LY_TEST_IMPACT_PERSISTENT_DIR}/active")
 
     # Historic persistent data dir
-    set(historic_dir "${PERSISTENT_DATA_DIR}/historic")
+    set(historic_dir "${LY_TEST_IMPACT_PERSISTENT_DIR}/historic")
 
     # Source to target mappings dir
     set(source_target_mapping_dir "${LY_TEST_IMPACT_SOURCE_TARGET_MAPPING_DIR}")
     
     # Test type artifact file
     set(test_target_type_file "${LY_TEST_IMPACT_TEST_TYPE_FILE}")
+
+    # Gem target file
+    set(gem_target_file "${LY_TEST_IMPACT_GEM_TARGET_FILE}")
     
     # Build dependency artifact dir
     set(target_dependency_dir "${LY_TEST_IMPACT_TARGET_DEPENDENCY_DIR}")
@@ -385,12 +430,10 @@ function(ly_test_impact_write_config_file CONFIG_TEMPLATE_FILE PERSISTENT_DATA_D
     
     # Write out entire config contents to a file in the build directory of the test impact framework console target
     file(GENERATE
-        OUTPUT "${PERSISTENT_DATA_DIR}/$<TARGET_FILE_BASE_NAME:${LY_TEST_IMPACT_CONSOLE_TARGET}>.$<CONFIG>.json" 
+        OUTPUT "${LY_TEST_IMPACT_CONFIG_FILE_PATH}" 
         CONTENT ${config_file}
     )
 
-    # Set the above config file as the default config file to use for the test impact framework console target
-    target_compile_definitions(${LY_TEST_IMPACT_CONSOLE_STATIC_TARGET} PUBLIC "LY_TEST_IMPACT_DEFAULT_CONFIG_FILE=\"${PERSISTENT_DATA_DIR}/$<TARGET_FILE_BASE_NAME:${LY_TEST_IMPACT_CONSOLE_TARGET}>.$<CONFIG>.json\"")
     message(DEBUG "Test impact framework post steps complete")
 endfunction()
 
@@ -400,13 +443,11 @@ function(ly_test_impact_post_step)
         return()
     endif()
 
-    # Directory per build config for persistent test impact data (to be checked in)
-    set(persistent_data_dir "${LY_TEST_IMPACT_WORKING_DIR}/persistent")
     # Directory for binaries built for this profile
     set(bin_dir "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIG>")
 
     # Erase any existing non-persistent data to avoid getting test impact framework out of sync with current repo state
-    file(REMOVE_RECURSE "${LY_TEST_IMPACT_WORKING_DIR}")
+    file(REMOVE_RECURSE "${LY_TEST_IMPACT_TEMP_DIR}")
 
     # Export the soruce to target mapping files
     ly_test_impact_export_source_target_mappings(
@@ -418,10 +459,14 @@ function(ly_test_impact_post_step)
         "cmake/TestImpactFramework/EnumeratedTests.in"
     )
 
+    # Export the enumerated gems
+    ly_test_impact_write_gem_target_enumeration_file(
+        "cmake/TestImpactFramework/EnumeratedGemTargets.in"
+    )
+
     # Write out the configuration file
     ly_test_impact_write_config_file(
         "cmake/TestImpactFramework/ConsoleFrontendConfig.in"
-        ${persistent_data_dir}
         ${bin_dir}
     )
     

+ 1 - 1
scripts/build/Jenkins/Jenkinsfile

@@ -476,7 +476,7 @@ try {
                         }
                     } else {
                         // Non-PR builds
-                        choice(defaultValue: DEFAULT_BUILD_SNAPSHOT, name: 'SNAPSHOT', choices: BUILD_SNAPSHOTS, description: 'Selects the build snapshot to use. A more diverted snapshot will cause longer build times, but will not cause build failures.')
+                        pipelineParameters.add(choice(defaultValue: DEFAULT_BUILD_SNAPSHOT, name: 'SNAPSHOT', choices: BUILD_SNAPSHOTS, description: 'Selects the build snapshot to use. A more diverted snapshot will cause longer build times, but will not cause build failures.'))
                         snapshot = env.SNAPSHOT
                         echo "Snapshot \"${snapshot}\" selected."
                     }

+ 5 - 3
scripts/build/Platform/Windows/build_config.json

@@ -33,7 +33,7 @@
     ],
     "steps": [
       "profile_vs2019",
-      "test_impact_analysis",
+      "test_impact_analysis_profile_vs2019",
       "asset_profile_vs2019",
       "test_cpu_profile_vs2019"
     ]
@@ -82,13 +82,15 @@
       "SCRIPT_PARAMETERS": "--platform 3rdParty --type 3rdParty_all"
     }
   },
-  "test_impact_analysis": {
+  "test_impact_analysis_profile_vs2019": {
     "TAGS": [
     ],
     "COMMAND": "python_windows.cmd",
     "PARAMETERS": {
+      "OUTPUT_DIRECTORY": "build/windows_vs2019",
+      "CONFIGURATION": "profile",
       "SCRIPT_PATH": "scripts/build/TestImpactAnalysis/tiaf_driver.py",
-      "SCRIPT_PARAMETERS": "--testFailurePolicy=continue --suite main --pipeline !PIPELINE_NAME! --destCommit !CHANGE_ID! --config \"build\\windows_vs2019\\bin\\TestImpactFramework\\persistent\\tiaf.profile.json\""
+      "SCRIPT_PARAMETERS": "--testFailurePolicy=continue --suite main --pipeline !PIPELINE_NAME! --destCommit !CHANGE_ID! --config \"!OUTPUT_DIRECTORY!/bin/TestImpactFramework/persistent/tiaf.profile.json\""
     }
   },
   "debug_vs2019": {