Merge branch 'TIF/Runtime' of https://github.com/aws-lumberyard-dev/o3de into TIF/Runtime
This commit is contained in:
@@ -11,3 +11,4 @@
|
||||
|
||||
add_subdirectory(Code)
|
||||
add_subdirectory(PythonTests)
|
||||
add_subdirectory(PythonCoverage)
|
||||
|
||||
@@ -55,4 +55,5 @@ set(ENABLED_GEMS
|
||||
AWSCore
|
||||
AWSClientAuth
|
||||
AWSMetrics
|
||||
PythonCoverage
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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,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
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa
|
||||
size 41127
|
||||
+1
-1
@@ -54,7 +54,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:
|
||||
|
||||
@@ -94,7 +94,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
@@ -29,16 +29,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,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>;
|
||||
}
|
||||
@@ -41,6 +41,7 @@ set(FILES
|
||||
API/EditorVegetationRequestsBus.h
|
||||
API/EditorPythonConsoleBus.h
|
||||
API/EditorPythonRunnerRequestsBus.h
|
||||
API/EditorPythonScriptNotificationsBus.h
|
||||
API/EntityPropertyEditorRequestsBus.h
|
||||
API/EditorWindowRequestBus.h
|
||||
API/EntityCompositionRequestBus.h
|
||||
|
||||
@@ -542,6 +542,7 @@ public:
|
||||
QString m_appRoot;
|
||||
QString m_logFile;
|
||||
QString m_pythonArgs;
|
||||
QString m_pythontTestCase;
|
||||
QString m_execFile;
|
||||
QString m_execLineCmd;
|
||||
|
||||
@@ -589,6 +590,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 },
|
||||
@@ -1527,7 +1529,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);
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
|
||||
#include <AzToolsFramework/API/EditorPythonScriptNotificationsBus.h>
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
@@ -579,6 +580,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;
|
||||
@@ -639,11 +643,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)
|
||||
{
|
||||
@@ -659,6 +667,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,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:
|
||||
|
||||
@@ -18,6 +18,9 @@ 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")
|
||||
|
||||
@@ -395,6 +398,7 @@ function(ly_test_impact_write_config_file CONFIG_TEMPLATE_FILE PERSISTENT_DATA_D
|
||||
|
||||
# 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\"")
|
||||
target_compile_definitions(${LY_TEST_IMPACT_PYTHON_COVERAGE_STATIC_TARGET} PRIVATE "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()
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user