Removed AutomatedLauncherTesting Gem and LauncherTestTools.
Signed-off-by: scspaldi <scspaldi@amazon.com>monroegm-disable-blank-issue-2
parent
f278bbcd65
commit
b2a4cf711e
@ -1,8 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
add_subdirectory(Code)
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
ly_add_target(
|
|
||||||
NAME AutomatedLauncherTesting.Static STATIC
|
|
||||||
NAMESPACE Gem
|
|
||||||
FILES_CMAKE
|
|
||||||
automatedlaunchertesting_files.cmake
|
|
||||||
INCLUDE_DIRECTORIES
|
|
||||||
PRIVATE
|
|
||||||
Source
|
|
||||||
PUBLIC
|
|
||||||
Include
|
|
||||||
BUILD_DEPENDENCIES
|
|
||||||
PUBLIC
|
|
||||||
AZ::AzCore
|
|
||||||
Legacy::CryCommon
|
|
||||||
Gem::LmbrCentral
|
|
||||||
)
|
|
||||||
|
|
||||||
ly_add_target(
|
|
||||||
NAME AutomatedLauncherTesting ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
|
|
||||||
NAMESPACE Gem
|
|
||||||
FILES_CMAKE
|
|
||||||
automatedlaunchertesting_shared_files.cmake
|
|
||||||
INCLUDE_DIRECTORIES
|
|
||||||
PRIVATE
|
|
||||||
Source
|
|
||||||
PUBLIC
|
|
||||||
Include
|
|
||||||
BUILD_DEPENDENCIES
|
|
||||||
PRIVATE
|
|
||||||
Gem::AutomatedLauncherTesting.Static
|
|
||||||
RUNTIME_DEPENDENCIES
|
|
||||||
Gem::LmbrCentral
|
|
||||||
)
|
|
||||||
|
|
||||||
# servers and clients use the above module.
|
|
||||||
ly_create_alias(NAME AutomatedLauncherTesting.Servers NAMESPACE Gem TARGETS Gem::AutomatedLauncherTesting)
|
|
||||||
ly_create_alias(NAME AutomatedLauncherTesting.Clients NAMESPACE Gem TARGETS Gem::AutomatedLauncherTesting)
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <AzCore/EBus/EBus.h>
|
|
||||||
|
|
||||||
namespace AutomatedLauncherTesting
|
|
||||||
{
|
|
||||||
class AutomatedLauncherTestingRequests
|
|
||||||
: public AZ::EBusTraits
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
|
||||||
// EBusTraits overrides
|
|
||||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
|
||||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Call this method from your test logic when a test is complete.
|
|
||||||
virtual void CompleteTest(bool success, const AZStd::string& message) = 0;
|
|
||||||
};
|
|
||||||
using AutomatedLauncherTestingRequestBus = AZ::EBus<AutomatedLauncherTestingRequests>;
|
|
||||||
} // namespace AutomatedLauncherTesting
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <AzCore/Memory/SystemAllocator.h>
|
|
||||||
#include <AzCore/Module/Module.h>
|
|
||||||
|
|
||||||
#include <AutomatedLauncherTestingSystemComponent.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace AutomatedLauncherTesting
|
|
||||||
{
|
|
||||||
class AutomatedLauncherTestingModule
|
|
||||||
: public AZ::Module
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AZ_RTTI(AutomatedLauncherTestingModule, "{3FC3E44A-0AC0-47C5-BD02-ADB2BA4338CA}", AZ::Module);
|
|
||||||
AZ_CLASS_ALLOCATOR(AutomatedLauncherTestingModule, AZ::SystemAllocator, 0);
|
|
||||||
|
|
||||||
AutomatedLauncherTestingModule()
|
|
||||||
: AZ::Module()
|
|
||||||
{
|
|
||||||
// Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
|
|
||||||
m_descriptors.insert(m_descriptors.end(), {
|
|
||||||
AutomatedLauncherTestingSystemComponent::CreateDescriptor(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add required SystemComponents to the SystemEntity.
|
|
||||||
*/
|
|
||||||
AZ::ComponentTypeList GetRequiredSystemComponents() const override
|
|
||||||
{
|
|
||||||
return AZ::ComponentTypeList{
|
|
||||||
azrtti_typeid<AutomatedLauncherTestingSystemComponent>(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
|
|
||||||
// The first parameter should be GemName_GemIdLower
|
|
||||||
// The second should be the fully qualified name of the class above
|
|
||||||
AZ_DECLARE_MODULE_CLASS(Gem_AutomatedLauncherTesting, AutomatedLauncherTesting::AutomatedLauncherTestingModule)
|
|
||||||
@ -1,230 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <AutomatedLauncherTestingSystemComponent.h>
|
|
||||||
|
|
||||||
#include <AzCore/Serialization/SerializeContext.h>
|
|
||||||
#include <AzCore/Serialization/EditContext.h>
|
|
||||||
#include <AzCore/Serialization/EditContextConstants.inl>
|
|
||||||
#include <AzCore/RTTI/BehaviorContext.h>
|
|
||||||
#include <AzFramework/StringFunc/StringFunc.h>
|
|
||||||
#include <CryCommon/platform.h>
|
|
||||||
#include <CryCommon/ICmdLine.h>
|
|
||||||
#include <CryCommon/IConsole.h>
|
|
||||||
#include <CryCommon/ISystem.h>
|
|
||||||
#include "SpawnDynamicSlice.h"
|
|
||||||
#include <AzCore/Component/Entity.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace AutomatedLauncherTesting
|
|
||||||
{
|
|
||||||
void AutomatedLauncherTestingSystemComponent::Reflect(AZ::ReflectContext* context)
|
|
||||||
{
|
|
||||||
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
|
|
||||||
{
|
|
||||||
serialize->Class<AutomatedLauncherTestingSystemComponent, AZ::Component>()
|
|
||||||
->Version(0);
|
|
||||||
|
|
||||||
if (AZ::EditContext* ec = serialize->GetEditContext())
|
|
||||||
{
|
|
||||||
ec->Class<AutomatedLauncherTestingSystemComponent>("AutomatedLauncherTesting", "[Description of functionality provided by this System Component]")
|
|
||||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
|
||||||
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System"))
|
|
||||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
|
||||||
{
|
|
||||||
behaviorContext->EBus<AutomatedLauncherTestingRequestBus>("AutomatedLauncherTestingRequestBus")
|
|
||||||
->Attribute(AZ::Script::Attributes::Category, "Testing")
|
|
||||||
->Event("CompleteTest", &AutomatedLauncherTestingRequestBus::Events::CompleteTest)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutomatedLauncherTestingSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
|
|
||||||
{
|
|
||||||
provided.push_back(AZ_CRC("AutomatedLauncherTestingService"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutomatedLauncherTestingSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
|
|
||||||
{
|
|
||||||
incompatible.push_back(AZ_CRC("AutomatedLauncherTestingService"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutomatedLauncherTestingSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
|
|
||||||
{
|
|
||||||
AZ_UNUSED(required);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutomatedLauncherTestingSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
|
|
||||||
{
|
|
||||||
AZ_UNUSED(dependent);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutomatedLauncherTestingSystemComponent::Init()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutomatedLauncherTestingSystemComponent::Activate()
|
|
||||||
{
|
|
||||||
AutomatedLauncherTestingRequestBus::Handler::BusConnect();
|
|
||||||
CrySystemEventBus::Handler::BusConnect();
|
|
||||||
AZ::TickBus::Handler::BusConnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutomatedLauncherTestingSystemComponent::Deactivate()
|
|
||||||
{
|
|
||||||
AZ::TickBus::Handler::BusDisconnect();
|
|
||||||
CrySystemEventBus::Handler::BusDisconnect();
|
|
||||||
AutomatedLauncherTestingRequestBus::Handler::BusDisconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutomatedLauncherTestingSystemComponent::CompleteTest(bool success, const AZStd::string& message)
|
|
||||||
{
|
|
||||||
AZ_Assert(
|
|
||||||
m_phase == Phase::RunningTest,
|
|
||||||
"Expected current phase to be RunningTest (%d), got %d, will skip printing CompleteTest message.",
|
|
||||||
Phase::RunningTest, m_phase);
|
|
||||||
|
|
||||||
if (m_phase == Phase::RunningTest)
|
|
||||||
{
|
|
||||||
if (!message.empty())
|
|
||||||
{
|
|
||||||
LogAlways("AutomatedLauncher: %s", message.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure this is always printed, in case log severity is turned down.
|
|
||||||
LogAlways("AutomatedLauncher: %s", success ? "AUTO_LAUNCHER_TEST_COMPLETE" : "AUTO_LAUNCHER_TEST_FAIL");
|
|
||||||
|
|
||||||
m_phase = Phase::Complete;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutomatedLauncherTestingSystemComponent::OnCrySystemInitialized(ISystem& system, [[maybe_unused]] const SSystemInitParams& systemInitParams)
|
|
||||||
{
|
|
||||||
m_system = &system;
|
|
||||||
// Only allow any testing to actually happen in non-release builds.
|
|
||||||
#if !defined(_RELEASE)
|
|
||||||
ICmdLine* cmdLine = m_system->GetICmdLine();
|
|
||||||
if (cmdLine)
|
|
||||||
{
|
|
||||||
AZ_Printf("AutomatedLauncher", "Checking for automated launcher testing command line arguments.");
|
|
||||||
const ICmdLineArg* mapArg = cmdLine->FindArg(eCLAT_Pre, "ltest_map");
|
|
||||||
if (mapArg)
|
|
||||||
{
|
|
||||||
AZStd::string map = mapArg->GetValue();
|
|
||||||
AZ_Printf("AutomatedLauncher", "Found ltest_map arg %s.", map.c_str());
|
|
||||||
if(map.compare("default") != 0)
|
|
||||||
{
|
|
||||||
AZStd::lock_guard<MutexType> lock(m_testOperationsMutex);
|
|
||||||
m_testOperations.push_back(TestOperation(TestOperationType::LoadMap, map.c_str()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Allow the default menu to load, watch for the next level to load
|
|
||||||
m_phase = Phase::LoadingMap;
|
|
||||||
m_nextLevelLoad = NextLevelLoad::WatchForNextLevelLoad;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ICmdLineArg* sliceArg = cmdLine->FindArg(eCLAT_Pre, "ltest_slice");
|
|
||||||
if (sliceArg)
|
|
||||||
{
|
|
||||||
AZStd::string slice = sliceArg->GetValue();
|
|
||||||
AZ_Printf("AutomatedLauncher", "Found ltest_slice arg %s.", slice.c_str());
|
|
||||||
|
|
||||||
AzFramework::StringFunc::Tokenize(slice.c_str(), m_slices, ",");
|
|
||||||
|
|
||||||
if (!m_slices.empty())
|
|
||||||
{
|
|
||||||
AZStd::lock_guard<MutexType> lock(m_testOperationsMutex);
|
|
||||||
m_testOperations.push_back(TestOperation(TestOperationType::SpawnDynamicSlice, m_slices[0].c_str()));
|
|
||||||
m_slices.erase(m_slices.begin());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutomatedLauncherTestingSystemComponent::OnCrySystemShutdown([[maybe_unused]] ISystem& system)
|
|
||||||
{
|
|
||||||
m_system = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutomatedLauncherTestingSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
|
|
||||||
{
|
|
||||||
// Check to see if there is a load map operation in flight
|
|
||||||
if (m_currentTestOperation.m_type == TestOperationType::LoadMap && !m_currentTestOperation.m_complete)
|
|
||||||
{
|
|
||||||
if (m_system->GetSystemGlobalState() == ESYSTEM_GLOBAL_STATE_LEVEL_LOAD_COMPLETE)
|
|
||||||
{
|
|
||||||
m_currentTestOperation.m_complete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Only start a new operation if there isn't already one in flight right now.
|
|
||||||
else if (!m_testOperations.empty())
|
|
||||||
{
|
|
||||||
// Grab the first operation from the list
|
|
||||||
{
|
|
||||||
AZStd::lock_guard<MutexType> lock(m_testOperationsMutex);
|
|
||||||
m_currentTestOperation = m_testOperations.at(0);
|
|
||||||
m_testOperations.erase(m_testOperations.begin());
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it was a map command, go ahead and launch it.
|
|
||||||
if (m_currentTestOperation.m_type == TestOperationType::LoadMap)
|
|
||||||
{
|
|
||||||
AZ_Assert(m_phase == Phase::None, "Expected current phase to be None (%d), got %d", Phase::None, m_phase);
|
|
||||||
AZStd::string command = AZStd::string::format("map %s", m_currentTestOperation.m_value.c_str());
|
|
||||||
m_system->GetIConsole()->ExecuteString(command.c_str());
|
|
||||||
m_phase = Phase::LoadingMap;
|
|
||||||
}
|
|
||||||
// If it was a spawn dynamic slice command, go ahead and spawn it.
|
|
||||||
else if (m_currentTestOperation.m_type == TestOperationType::SpawnDynamicSlice)
|
|
||||||
{
|
|
||||||
AZ_Assert((m_phase == Phase::LoadingMap) || (m_phase == Phase::RunningTest), "Expected current phase to be LoadMap or RunningTest (%d), got %d", Phase::LoadingMap, m_phase);
|
|
||||||
AZ::Entity* spawnedEntity = SpawnDynamicSlice::CreateSpawner(m_currentTestOperation.m_value, "Automated Testing Dynamic Slice Spawner");
|
|
||||||
if (spawnedEntity)
|
|
||||||
{
|
|
||||||
m_spawnedEntities.emplace_back(std::move(spawnedEntity));
|
|
||||||
}
|
|
||||||
m_phase = Phase::RunningTest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ((m_nextLevelLoad == NextLevelLoad::None) && (m_phase == Phase::RunningTest) && (m_system->GetSystemGlobalState() == ESYSTEM_GLOBAL_STATE_RUNNING))
|
|
||||||
{
|
|
||||||
AZ_Printf("AutomatedLauncher", "Running Test - Watching for a next level load");
|
|
||||||
m_nextLevelLoad = NextLevelLoad::WatchForNextLevelLoad;
|
|
||||||
}
|
|
||||||
else if ((m_nextLevelLoad == NextLevelLoad::WatchForNextLevelLoad) && (m_system->GetSystemGlobalState() == ESYSTEM_GLOBAL_STATE_LEVEL_LOAD_COMPLETE))
|
|
||||||
{
|
|
||||||
AZ_Printf("AutomatedLauncher", "Next level loaded, adding operations");
|
|
||||||
if (!m_slices.empty())
|
|
||||||
{
|
|
||||||
m_testOperations.push_back(TestOperation(TestOperationType::SpawnDynamicSlice, m_slices[0].c_str()));
|
|
||||||
m_slices.erase(m_slices.begin());
|
|
||||||
|
|
||||||
m_nextLevelLoad = NextLevelLoad::None;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_nextLevelLoad = NextLevelLoad::LevelLoadsComplete;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutomatedLauncherTestingSystemComponent::LogAlways(const char* format, ...)
|
|
||||||
{
|
|
||||||
va_list args;
|
|
||||||
va_start(args, format);
|
|
||||||
m_system->GetILog()->LogV(ILog::eAlways, format, args);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <AzCore/Component/Component.h>
|
|
||||||
|
|
||||||
#include <AutomatedLauncherTesting/AutomatedLauncherTestingBus.h>
|
|
||||||
#include <CrySystemBus.h>
|
|
||||||
#include <AzCore/Component/TickBus.h>
|
|
||||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
|
||||||
#include <AzCore/std/containers/vector.h>
|
|
||||||
|
|
||||||
namespace AZ
|
|
||||||
{
|
|
||||||
class Entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace AutomatedLauncherTesting
|
|
||||||
{
|
|
||||||
class AutomatedLauncherTestingSystemComponent
|
|
||||||
: public AZ::Component
|
|
||||||
, protected AutomatedLauncherTestingRequestBus::Handler
|
|
||||||
, private CrySystemEventBus::Handler
|
|
||||||
, private AZ::TickBus::Handler
|
|
||||||
{
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum class Phase
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
LoadingMap,
|
|
||||||
RunningTest,
|
|
||||||
Complete
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class NextLevelLoad
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
WatchForNextLevelLoad,
|
|
||||||
LevelLoadsComplete
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class TestOperationType
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
LoadMap,
|
|
||||||
SpawnDynamicSlice
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TestOperation
|
|
||||||
{
|
|
||||||
TestOperation()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
TestOperation(TestOperationType type, const AZStd::string& value)
|
|
||||||
: m_type(type)
|
|
||||||
, m_value(value)
|
|
||||||
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
TestOperationType m_type = TestOperationType::None;
|
|
||||||
AZStd::string m_value;
|
|
||||||
bool m_complete = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
AZ_COMPONENT(AutomatedLauncherTestingSystemComponent, "{87A405E2-390B-43A9-9A96-94BDC0DF680B}");
|
|
||||||
|
|
||||||
static void Reflect(AZ::ReflectContext* context);
|
|
||||||
|
|
||||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
|
|
||||||
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
|
|
||||||
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
|
|
||||||
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
|
||||||
// AutomatedLauncherTestingRequestBus interface implementation
|
|
||||||
void CompleteTest(bool success, const AZStd::string& message) override;
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
protected:
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
|
||||||
// CrySystemEvents
|
|
||||||
void OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) override;
|
|
||||||
void OnCrySystemShutdown(ISystem& system) override;
|
|
||||||
|
|
||||||
// AZ::Component interface implementation
|
|
||||||
void Init() override;
|
|
||||||
void Activate() override;
|
|
||||||
void Deactivate() override;
|
|
||||||
|
|
||||||
// TickBus
|
|
||||||
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
|
|
||||||
|
|
||||||
void LogAlways(const char* format, ...);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ISystem* m_system = nullptr;
|
|
||||||
AZStd::vector<TestOperation> m_testOperations;
|
|
||||||
AZStd::vector<AZStd::string> m_slices;
|
|
||||||
MutexType m_testOperationsMutex;
|
|
||||||
TestOperation m_currentTestOperation;
|
|
||||||
AZStd::vector<AZStd::shared_ptr<AZ::Entity>> m_spawnedEntities;
|
|
||||||
Phase m_phase = Phase::None;
|
|
||||||
NextLevelLoad m_nextLevelLoad = NextLevelLoad::None;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#include "SpawnDynamicSlice.h"
|
|
||||||
|
|
||||||
#include <AzCore/Asset/AssetManager.h>
|
|
||||||
#include <AzCore/Asset/AssetManagerBus.h>
|
|
||||||
#include <AzCore/Component/Entity.h>
|
|
||||||
#include <LmbrCentral/Scripting/SpawnerComponentBus.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace AutomatedLauncherTesting
|
|
||||||
{
|
|
||||||
AZ::Entity* SpawnDynamicSlice::CreateSpawner(const AZStd::string& path, const AZStd::string& entityName)
|
|
||||||
{
|
|
||||||
AZ::Entity* spawnerEntity = nullptr;
|
|
||||||
|
|
||||||
AZ::Data::AssetId sliceAssetId;
|
|
||||||
AZ::Data::AssetCatalogRequestBus::BroadcastResult(sliceAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, path.c_str(), AZ::Data::s_invalidAssetType, false);
|
|
||||||
if (sliceAssetId.IsValid())
|
|
||||||
{
|
|
||||||
AZ_Printf("System", "Spawning dynamic slide %s", path.c_str());
|
|
||||||
spawnerEntity = aznew AZ::Entity(entityName.c_str());
|
|
||||||
spawnerEntity->Init();
|
|
||||||
|
|
||||||
LmbrCentral::SpawnerConfig spawnerConfig;
|
|
||||||
|
|
||||||
AZ::Data::Asset<AZ::DynamicSliceAsset> sliceAssetData = AZ::Data::AssetManager::Instance().GetAsset<AZ::DynamicSliceAsset>(sliceAssetId, spawnerConfig.m_sliceAsset.GetAutoLoadBehavior());
|
|
||||||
sliceAssetData.BlockUntilLoadComplete();
|
|
||||||
|
|
||||||
AZ::Component* spawnerComponent = nullptr;
|
|
||||||
AZ::ComponentDescriptorBus::EventResult(spawnerComponent, LmbrCentral::SpawnerComponentTypeId, &AZ::ComponentDescriptorBus::Events::CreateComponent);
|
|
||||||
|
|
||||||
spawnerConfig.m_sliceAsset = sliceAssetData;
|
|
||||||
spawnerConfig.m_spawnOnActivate = true;
|
|
||||||
spawnerComponent->SetConfiguration(spawnerConfig);
|
|
||||||
|
|
||||||
spawnerEntity->AddComponent(spawnerComponent);
|
|
||||||
|
|
||||||
spawnerEntity->Activate();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AZ_Warning("System", false, "Could not create asset for dynamic slide %s", path.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
return spawnerEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace AutomatedLauncherTesting
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <AzCore/std/string/string.h>
|
|
||||||
|
|
||||||
namespace AZ
|
|
||||||
{
|
|
||||||
class Entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace AutomatedLauncherTesting
|
|
||||||
{
|
|
||||||
class SpawnDynamicSlice
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static AZ::Entity* CreateSpawner(const AZStd::string& path, const AZStd::string& entityName);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace AutomatedLauncherTesting
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
set(FILES
|
|
||||||
Include/AutomatedLauncherTesting/AutomatedLauncherTestingBus.h
|
|
||||||
Source/AutomatedLauncherTestingSystemComponent.cpp
|
|
||||||
Source/AutomatedLauncherTestingSystemComponent.h
|
|
||||||
Source/SpawnDynamicSlice.cpp
|
|
||||||
Source/SpawnDynamicSlice.h
|
|
||||||
)
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
set(FILES
|
|
||||||
Source/AutomatedLauncherTestingModule.cpp
|
|
||||||
)
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"gem_name": "AutomatedLauncherTesting",
|
|
||||||
"display_name": "Automated Launcher Testing",
|
|
||||||
"license": "Apache-2.0 Or MIT",
|
|
||||||
"origin": "Open 3D Engine - o3de.org",
|
|
||||||
"type": "Code",
|
|
||||||
"summary": "The Automated Launcher Testing Gem manages automated Open 3D Engine (O3DE) launcher tests.",
|
|
||||||
"canonical_tags": ["Gem"],
|
|
||||||
"user_tags": ["Debug", "Tools"],
|
|
||||||
"icon_path": "preview.png",
|
|
||||||
"requirements": ""
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa
|
|
||||||
size 41127
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
"""
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
|
|
||||||
Device Farm Create Bundle
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import stat
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def on_rm_error( func, path, exc_info):
|
|
||||||
# path contains the path of the file that couldn't be removed
|
|
||||||
# let's just assume that it's read-only and unlink it.
|
|
||||||
os.chmod( path, stat.S_IWRITE )
|
|
||||||
os.unlink( path )
|
|
||||||
|
|
||||||
def copy_python_code_tree(src, dest):
|
|
||||||
shutil.copytree(src, dest, ignore=shutil.ignore_patterns('*.pyc', '__pycache__'))
|
|
||||||
|
|
||||||
def create_test_bundle(project, project_launcher_tests_folder, python_test_tools_folder):
|
|
||||||
|
|
||||||
temp_folder = os.path.join('temp', project)
|
|
||||||
|
|
||||||
# Place all artifacts to send to device farm in this output folder
|
|
||||||
zip_output_folder = os.path.join(temp_folder, 'zip_output')
|
|
||||||
|
|
||||||
# clear the old virtual env folder
|
|
||||||
logger.info("deleting old zip folder ...")
|
|
||||||
if os.path.isdir(zip_output_folder):
|
|
||||||
logger.info("Removing virtual env folder \"{}\" ...".format(zip_output_folder))
|
|
||||||
shutil.rmtree(zip_output_folder, onerror = on_rm_error)
|
|
||||||
|
|
||||||
# create the output folder where we dump everything to be zipped up.
|
|
||||||
os.makedirs(zip_output_folder)
|
|
||||||
|
|
||||||
# core files to add (iOS won't be referenced on Android, but it won't hurt anything)
|
|
||||||
core_files = [
|
|
||||||
'run_launcher_tests.py',
|
|
||||||
'run_launcher_tests_ios.py',
|
|
||||||
'run_launcher_tests_android.py',
|
|
||||||
os.path.join('..', '..', project, 'project.json')]
|
|
||||||
for file in core_files:
|
|
||||||
shutil.copy2(file, os.path.join(zip_output_folder, os.path.basename(file)))
|
|
||||||
|
|
||||||
logger.info("Including test code ...")
|
|
||||||
test_output_folder = os.path.join(zip_output_folder, 'tests')
|
|
||||||
copy_python_code_tree(project_launcher_tests_folder, test_output_folder)
|
|
||||||
|
|
||||||
# Copy remote console from PythonTestTools
|
|
||||||
logger.info("Including python PythonTestTools remote console ...")
|
|
||||||
shutil.copy2(
|
|
||||||
os.path.join(python_test_tools_folder, 'shared', 'remote_console_commands.py'),
|
|
||||||
os.path.join(test_output_folder, 'remote_console_commands.py'))
|
|
||||||
|
|
||||||
# Zip the tests/ folder, wheelhouse/ folder, and the requirements.txt file into a single archive:
|
|
||||||
test_bundle_path = os.path.join(temp_folder, 'test_bundle')
|
|
||||||
logger.info("Generating test bundle zip {} ...".format(test_bundle_path))
|
|
||||||
shutil.make_archive(test_bundle_path, 'zip', zip_output_folder)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Create the test bundle zip file for use on the Device Farm.')
|
|
||||||
parser.add_argument('--project', required=True, help='Lumberyard Project')
|
|
||||||
parser.add_argument('--project-launcher-tests-folder', required=True, help='Absolute path of the folder that contains the test code source.')
|
|
||||||
parser.add_argument('--python-test-tools-folder', required=True, help='Absolute path of the PythonTestTools folder.')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
create_test_bundle(args.project, args.project_launcher_tests_folder, args.python_test_tools_folder)
|
|
||||||
|
|
||||||
if __name__== "__main__":
|
|
||||||
main()
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM
|
|
||||||
REM Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
REM
|
|
||||||
REM SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
REM
|
|
||||||
REM
|
|
||||||
REM
|
|
||||||
|
|
||||||
python device_farm_create_bundle.py --project StarterGame --project-launcher-tests-folder "../../StarterGame/LauncherTests" --python-test-tools-folder "../PythonTestTools/test_tools"
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"attribute": "ARN",
|
|
||||||
"operator": "IN",
|
|
||||||
"value": "[%DEVICE_ARN_LIST%]"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@ -1,286 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
|
|
||||||
Device Farm Schecule Run
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import requests
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def bake_template(filename, values):
|
|
||||||
"""Open a template and replace values. Return path to baked file."""
|
|
||||||
# Open the options json template and replace with real values.
|
|
||||||
with open(filename, 'r') as in_file:
|
|
||||||
data = in_file.read()
|
|
||||||
for key, value in values.iteritems():
|
|
||||||
data = data.replace(key, str(value))
|
|
||||||
filename_out = os.path.join('temp', filename)
|
|
||||||
with open(filename_out, 'w') as out_file:
|
|
||||||
out_file.write(data)
|
|
||||||
return filename_out
|
|
||||||
|
|
||||||
def execute_aws_command(args):
|
|
||||||
""" Execut the aws cli devicefarm command. """
|
|
||||||
# Use .cmd on Windows, not sure exactly why, but aws will not be found without it.
|
|
||||||
aws_executable = 'aws.cmd' if sys.platform.startswith('win') else 'aws'
|
|
||||||
aws_args = [aws_executable, 'devicefarm', '--region', 'us-west-2'] + args
|
|
||||||
logger.info("Running {} ...".format(" ".join(aws_args)))
|
|
||||||
p = subprocess.Popen(aws_args, stdout=subprocess.PIPE)
|
|
||||||
out, err = p.communicate()
|
|
||||||
if p.returncode != 0:
|
|
||||||
msg = "Command '{}' failed. return code: {} out: {} err: {}".format(
|
|
||||||
" ".join(aws_args),
|
|
||||||
p.returncode,
|
|
||||||
out,
|
|
||||||
err
|
|
||||||
)
|
|
||||||
raise Exception(msg)
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def find_or_create_project(project_name):
|
|
||||||
""" Find the project by name, or create a new one. """
|
|
||||||
list_projects_data = json.loads(execute_aws_command(['list-projects']))
|
|
||||||
|
|
||||||
# return the arn if it is found
|
|
||||||
for project_data in list_projects_data['projects']:
|
|
||||||
if project_data['name'] == project_name:
|
|
||||||
logger.info("Found existing project named {}.".format(project_name))
|
|
||||||
return project_data['arn']
|
|
||||||
|
|
||||||
# project not found, create a new project with the give name
|
|
||||||
project_data = json.loads(execute_aws_command(['create-project', '--name', project_name]))
|
|
||||||
return project_data['project']['arn']
|
|
||||||
|
|
||||||
def find_or_create_device_pool(project_name, device_pool_name, device_arns):
|
|
||||||
""" Find the device pool in the project by name, or create a new one. """
|
|
||||||
list_device_pools_data = json.loads(execute_aws_command(['list-device-pools', '--arn', project_name]))
|
|
||||||
|
|
||||||
# return the arn if it is found
|
|
||||||
for device_pool_data in list_device_pools_data['devicePools']:
|
|
||||||
if device_pool_data['name'] == device_pool_name:
|
|
||||||
logger.info("Found existing device pool named {}.".format(device_pool_name))
|
|
||||||
return device_pool_data['arn']
|
|
||||||
|
|
||||||
device_pool_json_path_out = bake_template(
|
|
||||||
'device_farm_default_device_pool_template.json',
|
|
||||||
{'%DEVICE_ARN_LIST%' : device_arns})
|
|
||||||
|
|
||||||
# create a default device pool
|
|
||||||
args = [
|
|
||||||
'create-device-pool',
|
|
||||||
'--project-arn',
|
|
||||||
project_name,
|
|
||||||
'--name',
|
|
||||||
device_pool_name,
|
|
||||||
'--rules',
|
|
||||||
"file://{}".format(device_pool_json_path_out)]
|
|
||||||
device_pools_data = json.loads(execute_aws_command(args))
|
|
||||||
return device_pools_data['devicePool']['arn']
|
|
||||||
|
|
||||||
|
|
||||||
def create_upload(project_arn, path, type):
|
|
||||||
""" Create an upload and return the ARN """
|
|
||||||
args = ['create-upload', '--project-arn', project_arn, '--name', os.path.basename(path), '--type', type]
|
|
||||||
upload_data = json.loads(execute_aws_command(args))
|
|
||||||
return upload_data['upload']['arn'], upload_data['upload']['url']
|
|
||||||
|
|
||||||
def send_upload(filename, url):
|
|
||||||
""" Upload a file with a put request. """
|
|
||||||
logger.info("Sending upload {} ...".format(filename))
|
|
||||||
with open(filename, 'rb') as uploadfile:
|
|
||||||
data = uploadfile.read()
|
|
||||||
headers = {"content-type": "application/octet-stream"}
|
|
||||||
output = requests.put(url, data=data, allow_redirects=True, headers=headers)
|
|
||||||
logger.info("Sent upload {}.".format(output))
|
|
||||||
|
|
||||||
def wait_for_upload_to_finish(poll_time, upload_arn):
|
|
||||||
""" Wait for an upload to finish by polling for status """
|
|
||||||
logger.info("Waiting for upload {} ...".format(upload_arn))
|
|
||||||
upload_data = json.loads(execute_aws_command(['get-upload', '--arn', upload_arn]))
|
|
||||||
while not upload_data['upload']['status'] in ['SUCCEEDED', 'FAILED']:
|
|
||||||
time.sleep(poll_time)
|
|
||||||
upload_data = json.loads(execute_aws_command(['get-upload', '--arn', upload_arn]))
|
|
||||||
|
|
||||||
if upload_data['upload']['status'] != 'SUCCEEDED':
|
|
||||||
raise Exception('Upload failed.')
|
|
||||||
|
|
||||||
def upload(poll_time, project_arn, path, type):
|
|
||||||
""" Create the upload on the Device Farm, upload the file and wait for completion. """
|
|
||||||
arn, url = create_upload(project_arn, path, type)
|
|
||||||
send_upload(path, url)
|
|
||||||
wait_for_upload_to_finish(poll_time, arn)
|
|
||||||
return arn
|
|
||||||
|
|
||||||
def schedule_run(project_arn, app_arn, device_pool_arn, test_spec_arn, test_bundle_arn, execution_timeout):
|
|
||||||
""" Schecule the test run on the Device Farm """
|
|
||||||
run_name = "LY LT {}".format(datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y"))
|
|
||||||
logger.info("Scheduling run {} ...".format(run_name))
|
|
||||||
|
|
||||||
schedule_run_test_json_path_out = bake_template(
|
|
||||||
'device_farm_schedule_run_test_template.json',
|
|
||||||
{'%TEST_SPEC_ARN%' : test_spec_arn, '%TEST_PACKAGE_ARN%' : test_bundle_arn})
|
|
||||||
|
|
||||||
execution_configuration_json_path_out = bake_template(
|
|
||||||
'device_farm_schedule_run_execution_configuration_template.json',
|
|
||||||
{'%EXECUTION_TIMEOUT%' : execution_timeout})
|
|
||||||
|
|
||||||
args = [
|
|
||||||
'schedule-run',
|
|
||||||
'--project-arn',
|
|
||||||
project_arn,
|
|
||||||
'--app-arn',
|
|
||||||
app_arn,
|
|
||||||
'--device-pool-arn',
|
|
||||||
device_pool_arn,
|
|
||||||
'--name',
|
|
||||||
"\"{}\"".format(run_name),
|
|
||||||
'--test',
|
|
||||||
"file://{}".format(schedule_run_test_json_path_out),
|
|
||||||
'--execution-configuration',
|
|
||||||
"file://{}".format(execution_configuration_json_path_out)]
|
|
||||||
|
|
||||||
schedule_run_data = json.loads(execute_aws_command(args))
|
|
||||||
return schedule_run_data['run']['arn']
|
|
||||||
|
|
||||||
def download_file(url, output_path):
|
|
||||||
""" download a file from a url, save in output_path """
|
|
||||||
try:
|
|
||||||
r = requests.get(url, stream=True)
|
|
||||||
r.raise_for_status()
|
|
||||||
output_folder = os.path.dirname(output_path)
|
|
||||||
if not os.path.exists(output_folder):
|
|
||||||
os.makedirs(output_folder)
|
|
||||||
with open(output_path, 'wb') as f:
|
|
||||||
for chunk in r.iter_content(chunk_size=8192):
|
|
||||||
if chunk:
|
|
||||||
f.write(chunk)
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
logging.exception("Failed request for downloading file from {}.".format(url))
|
|
||||||
return False
|
|
||||||
except IOError as e:
|
|
||||||
logging.exception("Failed writing to file {}.".format(output_path))
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def download_artifacts(run_arn, artifacts_output_folder):
|
|
||||||
"""
|
|
||||||
Download run artifacts and write to path set in artifacts_output_folder.
|
|
||||||
"""
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
list_jobs_data = json.loads(execute_aws_command(['list-jobs', '--arn', run_arn]))
|
|
||||||
for job_data in list_jobs_data['jobs']:
|
|
||||||
logger.info("Downloading artifacts for {} ...".format(job_data['name']))
|
|
||||||
safe_job_name = "".join(x for x in job_data['name'] if x.isalnum())
|
|
||||||
list_artifacts_data = json.loads(execute_aws_command(['list-artifacts', '--arn', job_data['arn'], '--type', 'FILE']))
|
|
||||||
for artifact_data in list_artifacts_data['artifacts']:
|
|
||||||
# A run may contain many jobs. Usually each job is one device type.
|
|
||||||
# Each job has 3 stages: setup, test and shutdown. You can tell what
|
|
||||||
# stage an artifact is from based on the ARN.
|
|
||||||
# We only care about artifacts from the main stage of the job,
|
|
||||||
# not the setup or tear down artifacts. So parse the ARN and look
|
|
||||||
# for the 00001 identifier.
|
|
||||||
print artifact_data['arn']
|
|
||||||
if artifact_data['arn'].split('/')[3] == '00001':
|
|
||||||
logger.info("Downloading artifacts {} ...".format(artifact_data['name']))
|
|
||||||
output_filename = "{}.{}".format(
|
|
||||||
"".join(x for x in artifact_data['name'] if x.isalnum()),
|
|
||||||
artifact_data['extension'])
|
|
||||||
output_path = os.path.join(artifacts_output_folder, safe_job_name, output_filename)
|
|
||||||
if not download_file(artifact_data['url'], output_path):
|
|
||||||
msg = "Failed to download file from {} and save to {}".format(artifact_data['url'], output_path)
|
|
||||||
logger.error(msg)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Upload and app and schedule a run on the Device Farm.')
|
|
||||||
parser.add_argument('--app-path', required=True, help='Path of the app file.')
|
|
||||||
parser.add_argument('--test-spec-path', required=True, help='Path of the test spec yaml.')
|
|
||||||
parser.add_argument('--test-bundle-path', required=True, help='Path of the test bundle zip.')
|
|
||||||
parser.add_argument('--project-name', required=True, help='The name of the project.')
|
|
||||||
parser.add_argument('--device-pool-name', required=True, help='The name of the device pool.')
|
|
||||||
parser.add_argument('--device-arns',
|
|
||||||
default='\\"arn:aws:devicefarm:us-west-2::device:6CCDF49186B64E3FB27B9346AC9FAEC1\\"',
|
|
||||||
help='List of device ARNs. Used when existing pool is not found by name. Default is Galaxy S8.')
|
|
||||||
parser.add_argument('--wait-for-result', default="true", help='Set to "true" to wait for result of run.')
|
|
||||||
parser.add_argument('--download-artifacts', default="true", help='Set to "true" to download artifacts after run. requires --wait-for-result')
|
|
||||||
parser.add_argument('--artifacts-output-folder', default="temp", help='Folder to place the downloaded artifacts.')
|
|
||||||
parser.add_argument('--upload-poll-time', default=10, help='How long to wait between polling upload status.')
|
|
||||||
parser.add_argument('--run-poll-time', default=60, help='How long to wait between polling run status.')
|
|
||||||
parser.add_argument('--run-execution-timeout', default=60, help='Run execution timeout.')
|
|
||||||
parser.add_argument('--test-names', nargs='+', help='A list of test names to run, default runs all tests.')
|
|
||||||
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
# Find the project by name, or create a new one.
|
|
||||||
project_arn = find_or_create_project(args.project_name)
|
|
||||||
|
|
||||||
# Find the device pool in the project by name, or create a new one.
|
|
||||||
device_pool_arn = find_or_create_device_pool(project_arn, args.device_pool_name, args.device_arns)
|
|
||||||
|
|
||||||
# Bake out EXTRA_ARGS option with args.test_names
|
|
||||||
extra_args = ""
|
|
||||||
if args.test_names:
|
|
||||||
extra_args = "--test-names {}".format(" ".join("\"{}\"".format(test_name) for test_name in args.test_names))
|
|
||||||
|
|
||||||
test_spec_path_out = bake_template(
|
|
||||||
args.test_spec_path,
|
|
||||||
{'%EXTRA_ARGS%' : extra_args})
|
|
||||||
|
|
||||||
# Upload test spec and test bundle (Appium js is just a generic avenue to our own custom code).
|
|
||||||
test_spec_arn = upload(args.upload_poll_time, project_arn, test_spec_path_out, 'APPIUM_NODE_TEST_SPEC')
|
|
||||||
test_bundle_arn = upload(args.upload_poll_time, project_arn, args.test_bundle_path, 'APPIUM_NODE_TEST_PACKAGE')
|
|
||||||
|
|
||||||
# Upload the app.
|
|
||||||
type = 'ANDROID_APP' if args.app_path.lower().endswith('.apk') else 'IOS_APP'
|
|
||||||
app_arn = upload(args.upload_poll_time, project_arn, args.app_path, type)
|
|
||||||
|
|
||||||
# Schedule the test run.
|
|
||||||
run_arn = schedule_run(project_arn, app_arn, device_pool_arn, test_spec_arn, test_bundle_arn, args.run_execution_timeout)
|
|
||||||
|
|
||||||
logger.info('Run scheduled.')
|
|
||||||
|
|
||||||
# Wait for run, exit with failure if test run fails.
|
|
||||||
# strcmp with true for easy of use jenkins boolean env var.
|
|
||||||
if args.wait_for_result.lower() == 'true':
|
|
||||||
|
|
||||||
# Runs can take a long time, so just poll once a mintue by default.
|
|
||||||
run_data = json.loads(execute_aws_command(['get-run', '--arn', run_arn]))
|
|
||||||
while run_data['run']['result'] == 'PENDING':
|
|
||||||
logger.info("Run status: {} waiting {} seconds ...".format(run_data['run']['result'], args.run_poll_time))
|
|
||||||
time.sleep(args.run_poll_time)
|
|
||||||
run_data = json.loads(execute_aws_command(['get-run', '--arn', run_arn]))
|
|
||||||
|
|
||||||
# Download run artifacts. strcmp with true for easy of use jenkins boolean env var.
|
|
||||||
if args.download_artifacts.lower() == 'true':
|
|
||||||
download_artifacts(run_arn, args.artifacts_output_folder)
|
|
||||||
|
|
||||||
# If the run did not pass raise an exception to fail this jenkins job.
|
|
||||||
if run_data['run']['result'] != 'PASSED':
|
|
||||||
# Dump all of the run info.
|
|
||||||
logger.info(run_data)
|
|
||||||
# Raise an exception to fail this test.
|
|
||||||
msg = "Run fail with result {}\nRun ARN: {}".format(run_data['run']['result'], run_arn)
|
|
||||||
raise Exception(msg)
|
|
||||||
|
|
||||||
logger.info('Run passed.')
|
|
||||||
|
|
||||||
if __name__== "__main__":
|
|
||||||
main()
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM
|
|
||||||
REM Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
REM
|
|
||||||
REM SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
REM
|
|
||||||
REM
|
|
||||||
REM
|
|
||||||
|
|
||||||
call ../../python/python.cmd run_launcher_tests_local_validation.py --dev-root-folder "../.." --project "StarterGame"
|
|
||||||
if %ERRORLEVEL% == 0 (
|
|
||||||
call ../../python/python.cmd device_farm_create_bundle.py --project StarterGame --project-launcher-tests-folder "../../StarterGame/LauncherTests" --python-test-tools-folder "../PythonTestTools/test_tools"
|
|
||||||
call ../../python/python.cmd device_farm_schedule_run.py --app-path "../../BinAndroidArmv8Clang/StarterGameLauncher_w_assets.apk" --project-name "LyAutomatedLauncher" --device-pool-name "LyAndroid" --test-spec-path "device_farm_test_spec_android.yaml" --test-bundle-path "temp/StarterGame/test_bundle.zip" --artifacts-output-folder "temp/StarterGame"
|
|
||||||
)
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"jobTimeoutMinutes": %EXECUTION_TIMEOUT%,
|
|
||||||
"videoCapture": true
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
#
|
|
||||||
|
|
||||||
../../python/python.sh run_launcher_tests_local_validation.py --dev-root-folder "../.." --project "StarterGame" || exit
|
|
||||||
../../python/python.sh device_farm_create_bundle.py --project StarterGame --project-launcher-tests-folder "../../StarterGame/LauncherTests" --python-test-tools-folder "../PythonTestTools/test_tools"
|
|
||||||
# Current known limitation on iOS, only one test at a time is supported, see run_launcher_tests_ios.py run_test
|
|
||||||
../../python/python.sh device_farm_schedule_run.py --app-path "$1" --project-name "LyAutomatedLauncherIOS" --device-pool-name "LyIOS" --test-spec-path "device_farm_test_spec_ios.yaml" --test-bundle-path "temp/StarterGame/test_bundle.zip" --artifacts-output-folder "temp/StarterGame" --device-arns "\\\"arn:aws:devicefarm:us-west-2::device:D125AEEE8614463BAE106865CAF4470E\\\"" --test-names "progress"
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "APPIUM_NODE",
|
|
||||||
"testPackageArn": "%TEST_PACKAGE_ARN%",
|
|
||||||
"testSpecArn": "%TEST_SPEC_ARN%"
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
version: 0.1
|
|
||||||
phases:
|
|
||||||
install:
|
|
||||||
commands:
|
|
||||||
|
|
||||||
pre_test:
|
|
||||||
commands:
|
|
||||||
- adb -P 5037 -s "$DEVICEFARM_DEVICE_UDID" install -r $DEVICEFARM_APP_PATH
|
|
||||||
|
|
||||||
test:
|
|
||||||
commands:
|
|
||||||
- python ./run_launcher_tests_android.py --project-json-path "./project.json" --project-launcher-tests-folder "./tests" --screenshots-folder "$SCREENSHOT_PATH" %EXTRA_ARGS%
|
|
||||||
|
|
||||||
post_test:
|
|
||||||
commands:
|
|
||||||
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
version: 0.1
|
|
||||||
phases:
|
|
||||||
install:
|
|
||||||
commands:
|
|
||||||
|
|
||||||
pre_test:
|
|
||||||
commands:
|
|
||||||
- idevicedebug -u $DEVICEFARM_DEVICE_UDID run com.amazon.lumberyard.startergame
|
|
||||||
- sleep 10s
|
|
||||||
|
|
||||||
test:
|
|
||||||
commands:
|
|
||||||
- python ./run_launcher_tests_ios.py --project-json-path "./project.json" --project-launcher-tests-folder "./tests" --screenshots-folder "$SCREENSHOT_PATH" %EXTRA_ARGS%
|
|
||||||
|
|
||||||
post_test:
|
|
||||||
commands:
|
|
||||||
@ -1,131 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class PlatformDriver:
|
|
||||||
|
|
||||||
def __init__(self, project_json_path, project_launcher_tests_folder, test_names, screenshots_folder, screenshots_interval):
|
|
||||||
self.project_json_path = project_json_path
|
|
||||||
self.project_launcher_tests_folder = project_launcher_tests_folder
|
|
||||||
self.test_names = test_names
|
|
||||||
self.screenshots_folder = screenshots_folder
|
|
||||||
self.screenshots_interval = screenshots_interval
|
|
||||||
|
|
||||||
def read_json_data(self, path):
|
|
||||||
"""Read a json file and return the data"""
|
|
||||||
try:
|
|
||||||
with open(path) as json_file:
|
|
||||||
json_data = json.load(json_file)
|
|
||||||
except Exception, e:
|
|
||||||
logger.error("Failed to read json file: '{}'".format(path))
|
|
||||||
logger.error("Exception: '{}'".format(e))
|
|
||||||
sys.exit(1)
|
|
||||||
return json_data
|
|
||||||
|
|
||||||
def read_project_name(self):
|
|
||||||
project_data = self.read_json_data(self.project_json_path)
|
|
||||||
return project_data['project_name']
|
|
||||||
|
|
||||||
|
|
||||||
def run_test(self, map, dynamic_slice, timeout, pass_string, fail_string):
|
|
||||||
""" Meant to be overridden in derived classes. """
|
|
||||||
return True
|
|
||||||
|
|
||||||
def run_launcher_tests(self):
|
|
||||||
"""Discovers all of the available tests in launcher_tests.json and runs them.
|
|
||||||
"""
|
|
||||||
# Delete the old screenshots folder if it exists
|
|
||||||
if os.path.exists(self.screenshots_folder):
|
|
||||||
shutil.rmtree(self.screenshots_folder)
|
|
||||||
|
|
||||||
# Read the launcher_tests.json file.
|
|
||||||
launcher_tests_data = self.read_json_data(os.path.join(self.project_launcher_tests_folder, 'launcher_tests.json'))
|
|
||||||
|
|
||||||
# Run each of the tests found in the launcher_tests.json file.
|
|
||||||
ok = True
|
|
||||||
for launcher_test_data in launcher_tests_data['launcher_tests']:
|
|
||||||
|
|
||||||
# Skip over this test if specific tests are specified and this is not one of them.
|
|
||||||
if self.test_names and launcher_test_data.get('name').lower() not in [x.lower() for x in self.test_names]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
ok = ok and self.run_test(
|
|
||||||
launcher_test_data.get('name'),
|
|
||||||
launcher_test_data.get('map'),
|
|
||||||
launcher_test_data.get('dynamic_slice'),
|
|
||||||
launcher_test_data.get('timeout'),
|
|
||||||
launcher_test_data.get('pass_string', 'AUTO_LAUNCHER_TEST_COMPLETE'),
|
|
||||||
launcher_test_data.get('fail_string', 'AUTO_LAUNCHER_TEST_FAIL'))
|
|
||||||
return ok
|
|
||||||
|
|
||||||
def monitor_process_output(self, test_name, command, pass_string, fail_string, timeout, log_file=None):
|
|
||||||
|
|
||||||
self.process = subprocess.Popen(command, stdout=subprocess.PIPE)
|
|
||||||
|
|
||||||
# On windows, function was failing (I think) because it checked the poll before the process
|
|
||||||
# had a chance to start, so added a short delay to give it some time to startup.
|
|
||||||
# It also failed if the log_file was open()'d when it didn't exist yet.
|
|
||||||
# Delay seems to have fixed the problem. 0.25 sec was too short.
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
if log_file:
|
|
||||||
# The process we're starting sends it's output to the log file instead of stdout
|
|
||||||
# so we need to monitor that instead of the stdout.
|
|
||||||
fp = open(log_file)
|
|
||||||
|
|
||||||
# Detect log output messages or timeout exceeded
|
|
||||||
start = time.time()
|
|
||||||
last_time = start
|
|
||||||
screenshot_time_remaining = self.screenshots_interval
|
|
||||||
screenshot_index = 0
|
|
||||||
logger.info('Waiting for test to complete.')
|
|
||||||
message = ""
|
|
||||||
result = False
|
|
||||||
while True:
|
|
||||||
if log_file:
|
|
||||||
line = fp.readline()
|
|
||||||
else:
|
|
||||||
line = self.process.stdout.readline()
|
|
||||||
if line == '' and self.process.poll() is not None:
|
|
||||||
break
|
|
||||||
if line:
|
|
||||||
logger.info(line.rstrip())
|
|
||||||
if pass_string in line:
|
|
||||||
message = "Detected {}. Test completed.".format(pass_string)
|
|
||||||
result = True
|
|
||||||
break
|
|
||||||
if fail_string in line:
|
|
||||||
message = "Detected {}. Test failed.".format(fail_string)
|
|
||||||
break
|
|
||||||
if time.time() - start > timeout:
|
|
||||||
message = "Timeout of {} reached. Test failed.".format(timeout)
|
|
||||||
break
|
|
||||||
|
|
||||||
rc = self.process.poll()
|
|
||||||
cur_time = time.time()
|
|
||||||
screenshot_time_remaining = screenshot_time_remaining - (cur_time - last_time)
|
|
||||||
last_time = cur_time
|
|
||||||
if screenshot_time_remaining <= 0:
|
|
||||||
self.take_screenshot(os.path.join(self.screenshots_folder, "{}_screen{}".format(test_name.replace(' ', '_'), screenshot_index)))
|
|
||||||
screenshot_index = screenshot_index + 1
|
|
||||||
screenshot_time_remaining = self.screenshots_interval
|
|
||||||
|
|
||||||
logger.info(message)
|
|
||||||
|
|
||||||
if log_file:
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
return result
|
|
||||||
@ -1,108 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import itertools
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from run_launcher_tests import PlatformDriver
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class AndroidDriver(PlatformDriver):
|
|
||||||
|
|
||||||
def read_package_name(self):
|
|
||||||
project_data = self.read_json_data(self.project_json_path)
|
|
||||||
return project_data['android_settings']['package_name']
|
|
||||||
|
|
||||||
def run_test(self, test_name, map, dynamic_slice, timeout, pass_string, fail_string):
|
|
||||||
|
|
||||||
package = self.read_package_name()
|
|
||||||
project = self.read_project_name()
|
|
||||||
package_and_activity = "{}/{}.{}Activity".format(package, package, project)
|
|
||||||
|
|
||||||
# clear any old logcat
|
|
||||||
command_line = ['adb', 'logcat', '-c']
|
|
||||||
p = subprocess.Popen(command_line)
|
|
||||||
p.communicate()
|
|
||||||
|
|
||||||
# increase ring buffer size because we are going to be printing a large amount of data in a short time.
|
|
||||||
command_line = ['adb', 'logcat', '-G', '10m']
|
|
||||||
p = subprocess.Popen(command_line)
|
|
||||||
p.communicate()
|
|
||||||
|
|
||||||
# Start the process and pass in the test args
|
|
||||||
logger.info("Start the activity {} ...".format(package_and_activity))
|
|
||||||
command_line = ['adb', 'shell', 'am', 'start', '-a', 'android.intent.action.MAIN', '-n', package_and_activity]
|
|
||||||
command_line += ['-e', 'ltest_map', map]
|
|
||||||
command_line += ['-e', 'ltest_slice', dynamic_slice]
|
|
||||||
p = subprocess.Popen(command_line)
|
|
||||||
p.communicate()
|
|
||||||
|
|
||||||
# Get the pid of the app to use in the logcat monitoring. If we don't
|
|
||||||
# do this we might get residual output from previous test run. Even
|
|
||||||
# though we do a clear.
|
|
||||||
for _ in itertools.repeat(None, 10):
|
|
||||||
command_line = ['adb', 'shell', 'pidof', package]
|
|
||||||
p = subprocess.Popen(command_line, stdout=subprocess.PIPE)
|
|
||||||
stdoutdata, stderrdata = p.communicate()
|
|
||||||
pid = stdoutdata.strip()
|
|
||||||
if pid:
|
|
||||||
logger.info("Get pid of {}".format(pid))
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.info('Failed to get pid, waiting 1 second to retry ...')
|
|
||||||
time.sleep(1)
|
|
||||||
if not pid:
|
|
||||||
raise Exception('Unable to determin the pid of the process.')
|
|
||||||
|
|
||||||
command_line = ['adb', '-d', 'logcat', "--pid={}".format(pid), 'LMBR:I', '*:S']
|
|
||||||
test_result = self.monitor_process_output(test_name, command_line, pass_string, fail_string, timeout)
|
|
||||||
|
|
||||||
logger.info("Kill the app ...")
|
|
||||||
p = subprocess.Popen(['adb', 'shell', 'am', 'force-stop', package])
|
|
||||||
p.communicate()
|
|
||||||
|
|
||||||
# Stop here if we failed.
|
|
||||||
if not test_result:
|
|
||||||
raise Exception("Test failed.")
|
|
||||||
|
|
||||||
def take_screenshot(self, output_path_no_ext):
|
|
||||||
# Create the output folder if it is not there
|
|
||||||
output_folder = os.path.dirname(output_path_no_ext)
|
|
||||||
if not os.path.exists(output_folder):
|
|
||||||
os.makedirs(output_folder)
|
|
||||||
|
|
||||||
# Take the screenshot
|
|
||||||
p = subprocess.Popen(['adb', 'shell', 'screencap', '-p', '/sdcard/screen.png'])
|
|
||||||
p.communicate()
|
|
||||||
|
|
||||||
# copy it off of the device
|
|
||||||
p = subprocess.Popen(['adb', 'pull', '/sdcard/screen.png', "{}.png".format(output_path_no_ext)])
|
|
||||||
p.communicate()
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Sets up and runs Android Launcher Tests.')
|
|
||||||
parser.add_argument('--project-json-path', required=True, help='Path to the project.json project settings file.')
|
|
||||||
parser.add_argument('--project-launcher-tests-folder', required=True, help='Path to the LauncherTests folder in a Project.')
|
|
||||||
parser.add_argument('--test-names', nargs='+', help='A list of test names to run, default runs all tests.')
|
|
||||||
parser.add_argument('--screenshots-folder', default="./temp/Android/screenshots", help='Output folder for screenshots.')
|
|
||||||
parser.add_argument('--screenshots-interval', default=5, help='Time interval between taking screenshots in seconds.')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
logger.info("Running Launcher tests at {} ...".format(args.project_launcher_tests_folder))
|
|
||||||
driver = AndroidDriver(args.project_json_path, args.project_launcher_tests_folder, args.test_names, args.screenshots_folder, args.screenshots_interval)
|
|
||||||
driver.run_launcher_tests()
|
|
||||||
|
|
||||||
if __name__== "__main__":
|
|
||||||
main()
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from run_launcher_tests import PlatformDriver
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class IOSDriver(PlatformDriver):
|
|
||||||
|
|
||||||
def __init__(self, project_json_path, project_launcher_tests_folder, test_names, screenshots_folder, screenshots_interval, device_udid):
|
|
||||||
self.project_json_path = project_json_path
|
|
||||||
self.project_launcher_tests_folder = project_launcher_tests_folder
|
|
||||||
self.test_names = test_names
|
|
||||||
self.screenshots_folder = screenshots_folder
|
|
||||||
self.screenshots_interval = screenshots_interval
|
|
||||||
self.device_udid = device_udid
|
|
||||||
|
|
||||||
def run_test(self, test_name, map, dynamic_slice, timeout, pass_string, fail_string):
|
|
||||||
|
|
||||||
project = self.read_project_name()
|
|
||||||
bundle_id = "com.amazon.lumberyard.{}".format(project)
|
|
||||||
|
|
||||||
# Start the process and pass in the test args
|
|
||||||
command_line = ['idevicedebug', '-u', self.device_udid, 'run', bundle_id]
|
|
||||||
command_line += ['-ltest_map', map]
|
|
||||||
command_line += ['-ltest_slice', dynamic_slice]
|
|
||||||
test_result = self.monitor_process_output(test_name, command_line, pass_string, fail_string, timeout)
|
|
||||||
|
|
||||||
# TODO: Figure out some way to kill the running app. Because we dont know how to do this,
|
|
||||||
# we currently have a limitation on iOS we can only run one test at a time.
|
|
||||||
|
|
||||||
# Stop here if we failed.
|
|
||||||
if not test_result:
|
|
||||||
raise Exception("Test failed.")
|
|
||||||
|
|
||||||
def take_screenshot(self, output_path_no_ext):
|
|
||||||
# Create the output folder if it is not there
|
|
||||||
output_folder = os.path.dirname(output_path_no_ext)
|
|
||||||
if not os.path.exists(output_folder):
|
|
||||||
os.makedirs(output_folder)
|
|
||||||
|
|
||||||
# idevicescreenshot to take a screenshot and save to output path.
|
|
||||||
p = subprocess.Popen(['idevicescreenshot', "{}.tiff".format(output_path_no_ext)])
|
|
||||||
p.communicate()
|
|
||||||
|
|
||||||
def discover_device():
|
|
||||||
""" Discover the connected device and get the UDID."""
|
|
||||||
logger.info("Getting the connected device UDID ...")
|
|
||||||
p = subprocess.Popen(['idevice_id', '--list'], stdout=subprocess.PIPE)
|
|
||||||
out, err = p.communicate()
|
|
||||||
if not out:
|
|
||||||
raise Exception("No output.\nout:{}\nerr:{}\n".format(out, err))
|
|
||||||
|
|
||||||
lines = out.splitlines()
|
|
||||||
if not len(lines):
|
|
||||||
raise Exception("No devices connected.\nout:{}\nerr:{}\n".format(out, err))
|
|
||||||
if len(lines) != 1:
|
|
||||||
raise Exception("More than one device connected. Use --device-udid\nout:{}\nerr:{}\n".format(out, err))
|
|
||||||
return lines[0]
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Sets up and runs iOS Launcher Tests.')
|
|
||||||
parser.add_argument('--project-json-path', required=True, help='Path to the project.json project settings file.')
|
|
||||||
parser.add_argument('--project-launcher-tests-folder', required=True, help='Path to the LauncherTests folder in a Project.')
|
|
||||||
parser.add_argument('--device-udid', help='The UDID of the iOS device. Will auto detect if just one device is attached.')
|
|
||||||
parser.add_argument('--test-names', nargs='+', help='A list of test names to run, default runs all tests.')
|
|
||||||
parser.add_argument('--screenshots-folder', default="./temp/iOS/screenshots", help='Output folder for screenshots.')
|
|
||||||
parser.add_argument('--screenshots-interval', default=5, help='Time interval between taking screenshots in seconds.')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
device_udid = args.device_udid
|
|
||||||
if not device_udid:
|
|
||||||
device_udid = discover_device()
|
|
||||||
|
|
||||||
logger.info("Running Launcher tests at {} ...".format(args.project_launcher_tests_folder))
|
|
||||||
driver = IOSDriver(args.project_json_path, args.project_launcher_tests_folder, args.test_names, args.screenshots_folder, args.screenshots_interval, device_udid)
|
|
||||||
driver.run_launcher_tests()
|
|
||||||
|
|
||||||
if __name__== "__main__":
|
|
||||||
main()
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def report_error_and_exit(msg):
|
|
||||||
"""Log an error message and exit with error code 1."""
|
|
||||||
logger.error(msg)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def check_autoexe_cfg(dev_root_folder, project):
|
|
||||||
"""
|
|
||||||
Make sure that the project's autoexec.cfg does not contain a Map command.
|
|
||||||
The Launcher Test framework is responsible for loading the map.
|
|
||||||
"""
|
|
||||||
# Open the autoexec.cfg file and read the contents
|
|
||||||
autoexec_cfg_path = os.path.join(dev_root_folder, project, 'autoexec.cfg')
|
|
||||||
try:
|
|
||||||
with open(autoexec_cfg_path) as f:
|
|
||||||
content = f.readlines()
|
|
||||||
except:
|
|
||||||
report_error_and_exit("Failed to read contents of {}".format(autoexec_cfg_path))
|
|
||||||
|
|
||||||
# Make sure no map command is detected, the Launcher Test code will be in charge of loading a map
|
|
||||||
for line in content:
|
|
||||||
if line.lower().startswith('map '):
|
|
||||||
report_error_and_exit("Map command '{}' detected in {}".format(line.strip(), autoexec_cfg_path))
|
|
||||||
|
|
||||||
def check_gems_enabled(dev_root_folder, project):
|
|
||||||
"""Check the project's gems to make sure the AutomatedLauncherTesting gem is enabled."""
|
|
||||||
# Read the gems.json file
|
|
||||||
gems_json_path = os.path.join(dev_root_folder, project, 'gems.json')
|
|
||||||
try:
|
|
||||||
with open(gems_json_path) as f:
|
|
||||||
json_data = json.load(f)
|
|
||||||
except:
|
|
||||||
report_error_and_exit("Failed to read contents of {}".format(gems_json_path))
|
|
||||||
|
|
||||||
# Make sure AutomatedLauncherTesting is enabled
|
|
||||||
found = False
|
|
||||||
for gem_data in json_data['Gems']:
|
|
||||||
if 'AutomatedLauncherTesting' in gem_data['Path']:
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not found:
|
|
||||||
report_error_and_exit("Automated Launcer Testing GEM not enabled in {}".format(gems_json_path))
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Run validation on the local environment to check for required Launcher Tests config.')
|
|
||||||
parser.add_argument('--dev-root-folder', required=True, help='Path to the root Lumberyard dev folder.')
|
|
||||||
parser.add_argument('--project', required=True, help='Lumberyard project.')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
logger.info("Running validation for project {} ...".format(args.project))
|
|
||||||
|
|
||||||
check_autoexe_cfg(args.dev_root_folder, args.project)
|
|
||||||
|
|
||||||
check_gems_enabled(args.dev_root_folder, args.project)
|
|
||||||
|
|
||||||
logger.info('Validation complete.')
|
|
||||||
|
|
||||||
if __name__== '__main__':
|
|
||||||
main()
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
"""
|
|
||||||
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from run_launcher_tests import PlatformDriver
|
|
||||||
|
|
||||||
import test_tools.platforms.win.win as win
|
|
||||||
from test_tools.shared.platform_map import PLATFORM_MAP
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class WinDriver(PlatformDriver):
|
|
||||||
|
|
||||||
def __init__(self, project_json_path, project_launcher_tests_folder):
|
|
||||||
self.project_json_path = project_json_path
|
|
||||||
self.project_launcher_tests_folder = project_launcher_tests_folder
|
|
||||||
|
|
||||||
self.platform_map = PLATFORM_MAP[win.platform_name() + "_" + win.default_compiler_option()]
|
|
||||||
|
|
||||||
|
|
||||||
def run_test(self, map, dynamic_slice, timeout, pass_string, fail_string):
|
|
||||||
|
|
||||||
project = self.read_project_name()
|
|
||||||
|
|
||||||
# Start the process and pass in the test args
|
|
||||||
|
|
||||||
dev_dir = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
dev_dir = os.path.join(dev_dir, os.path.pardir)
|
|
||||||
dev_dir = os.path.join(dev_dir, os.path.pardir)
|
|
||||||
dev_dir = os.path.realpath(dev_dir)
|
|
||||||
launcher_dir = os.path.join(dev_dir, self.platform_map["bin_dir"])
|
|
||||||
command_line = [os.path.join(launcher_dir, project + 'Launcher.exe')]
|
|
||||||
command_line += ['-ltest_map', map]
|
|
||||||
command_line += ['-ltest_slice', dynamic_slice]
|
|
||||||
log_file = os.path.join(dev_dir, "cache", project, "pc", "user", "log", "Game.log")
|
|
||||||
test_result = self.monitor_process_output(command_line, pass_string, fail_string, timeout, log_file)
|
|
||||||
|
|
||||||
if self.process:
|
|
||||||
self.process.kill()
|
|
||||||
|
|
||||||
# Stop here if we failed.
|
|
||||||
if not test_result:
|
|
||||||
raise Exception("Test failed.")
|
|
||||||
|
|
||||||
return test_result
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Sets up and runs Windows Launcher Tests.')
|
|
||||||
parser.add_argument('--project-json-path', required=True, help='Path to the project.json project settings file.')
|
|
||||||
parser.add_argument('--project-launcher-tests-folder', required=True, help='Path to the LauncherTests folder in a Project.')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
logger.info("Running Launcher tests at {} ...".format(args.project_launcher_tests_folder))
|
|
||||||
driver = WinDriver(args.project_json_path, args.project_launcher_tests_folder)
|
|
||||||
driver.run_launcher_tests()
|
|
||||||
|
|
||||||
if __name__== "__main__":
|
|
||||||
main()
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM
|
|
||||||
REM Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
||||||
REM
|
|
||||||
REM SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
||||||
REM
|
|
||||||
REM
|
|
||||||
REM
|
|
||||||
|
|
||||||
REM Provided as an example of how to run the Automated Launcher Test on a developer's local machine.
|
|
||||||
../../python/python.cmd run_launcher_tests_win.py --project-json-path "../../SamplesProject/project.json" --project-launcher-tests-folder "../../SamplesProject/LauncherTests"
|
|
||||||
Loading…
Reference in New Issue