From b38ab6c2bccec0f9c73db205fcff2eb0f4dafd07 Mon Sep 17 00:00:00 2001 From: Walters Date: Tue, 20 Apr 2021 22:17:11 -0700 Subject: [PATCH 1/4] Change reservation strategy in MotionInstancePool to reduce fragmentation --- Gems/EMotionFX/Code/EMotionFX/Source/MotionInstancePool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/MotionInstancePool.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/MotionInstancePool.cpp index cbe13308d9..d715fabd1c 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/MotionInstancePool.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/MotionInstancePool.cpp @@ -220,7 +220,7 @@ namespace EMotionFX //mPool->mFreeList.Reserve( numInstances * 2 ); if (mPool->mFreeList.GetMaxLength() < mPool->mNumInstances) { - mPool->mFreeList.Reserve(mPool->mNumInstances); + mPool->mFreeList.Reserve(mPool->mNumInstances + mPool->mFreeList.GetMaxLength() / 2); } mPool->mFreeList.ResizeFast(startIndex + numInstances); From 7f81602fe7189cc2f84ae73750eaf0eaeae656c2 Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Mon, 3 May 2021 15:43:42 -0700 Subject: [PATCH 2/4] Use the material id from the base mesh when optimizing blend shapes (#517) This is cherry-picked from #311 When processing meshes with blend shapes, the mesh optimizer disables the optimize duplicates setting, to prevent potential vertex reodering that could cause the base mesh vertices to become out of sync with the blend shape. However, it will still reorder vertices based on their material. It places all triangles that use the same material in the same submesh, grouping them together in the resulting mesh. The SceneAPI does not track material ids for blend shapes. To ensure that the blend shape triangles are reordered in the same way as the base shape, this change makes the blend shape optimization use the material id from the base shape. --- .../MeshOptimizer/MeshOptimizerComponent.cpp | 16 ++++------------ .../MeshOptimizer/MeshOptimizerComponent.h | 4 +--- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp index 8be67fe89b..592a8e2a75 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp @@ -290,7 +290,7 @@ namespace AZ::SceneGenerationComponents const bool hasBlendShapes = HasAnyBlendShapeChild(graph, nodeIndex); - auto [optimizedMesh, optimizedUVs, optimizedTangents, optimizedBitangents, optimizedVertexColors, optimizedSkinWeights] = OptimizeMesh(mesh, uvDatas, tangentDatas, bitangentDatas, colorDatas, skinWeightDatas, meshGroup, hasBlendShapes); + auto [optimizedMesh, optimizedUVs, optimizedTangents, optimizedBitangents, optimizedVertexColors, optimizedSkinWeights] = OptimizeMesh(mesh, mesh, uvDatas, tangentDatas, bitangentDatas, colorDatas, skinWeightDatas, meshGroup, hasBlendShapes); const NodeIndex optimizedMeshNodeIndex = graph.AddChild(graph.GetNodeParent(nodeIndex), name.c_str(), AZStd::move(optimizedMesh)); @@ -322,7 +322,7 @@ namespace AZ::SceneGenerationComponents for (const NodeIndex& blendShapeNodeIndex : nodeIndexes(Containers::MakeDerivedFilterView(childNodes(nodeIndex)))) { const IBlendShapeData* blendShapeNode = static_cast(graph.GetNodeContent(blendShapeNodeIndex).get()); - auto [optimizedBlendShape, _1, _2, _3 , _4, _5] = OptimizeMesh(blendShapeNode, {}, {}, {}, {}, {}, meshGroup, hasBlendShapes); + auto [optimizedBlendShape, _1, _2, _3 , _4, _5] = OptimizeMesh(blendShapeNode, mesh, {}, {}, {}, {}, {}, meshGroup, hasBlendShapes); const AZStd::string optimizedName {graph.GetNodeName(blendShapeNodeIndex).GetName(), graph.GetNodeName(blendShapeNodeIndex).GetNameLength()}; const NodeIndex optimizedNodeIndex = graph.AddChild(optimizedMeshNodeIndex, optimizedName.c_str(), AZStd::move(optimizedBlendShape)); @@ -383,6 +383,7 @@ namespace AZ::SceneGenerationComponents AZStd::unique_ptr > MeshOptimizerComponent::OptimizeMesh( const MeshDataType* meshData, + const IMeshData* baseMesh, const AZStd::vector>& uvs, const AZStd::vector>& tangents, const AZStd::vector>& bitangents, @@ -441,7 +442,7 @@ namespace AZ::SceneGenerationComponents const AZ::u32 faceCount = meshData->GetFaceCount(); for (AZ::u32 faceIndex = 0; faceIndex < faceCount; ++faceIndex) { - meshBuilder.BeginPolygon(GetFaceMaterialId(meshData, faceIndex)); + meshBuilder.BeginPolygon(baseMesh->GetFaceMaterialId(faceIndex)); for (const AZ::u32 vertexIndex : meshData->GetFaceInfo(faceIndex).vertexIndex) { const int orgVertexNumber = meshData->GetUsedPointIndexForControlPoint(meshData->GetControlPointIndex(vertexIndex)); @@ -584,15 +585,6 @@ namespace AZ::SceneGenerationComponents ); } - unsigned int MeshOptimizerComponent::GetFaceMaterialId([[maybe_unused]] const AZ::SceneAPI::DataTypes::IBlendShapeData* meshData, [[maybe_unused]] unsigned int index) - { - return 0; - } - unsigned int MeshOptimizerComponent::GetFaceMaterialId(const AZ::SceneAPI::DataTypes::IMeshData* meshData, unsigned int index) - { - return meshData->GetFaceMaterialId(index); - } - void MeshOptimizerComponent::AddFace(AZ::SceneData::GraphData::BlendShapeData* blendShape, unsigned int index1, unsigned int index2, unsigned int index3, [[maybe_unused]] unsigned int faceMaterialId) { blendShape->AddFace({index1, index2, index3}); diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.h b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.h index ee8fa5afe5..e499f30e2e 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.h +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.h @@ -66,6 +66,7 @@ namespace AZ::SceneGenerationComponents AZStd::unique_ptr > OptimizeMesh( const MeshDataType* meshData, + const SceneAPI::DataTypes::IMeshData* baseMesh, const AZStd::vector>& uvs, const AZStd::vector>& tangents, const AZStd::vector>& bitangents, @@ -74,9 +75,6 @@ namespace AZ::SceneGenerationComponents const AZ::SceneAPI::DataTypes::IMeshGroup& meshGroup, bool hasBlendShapes); - static unsigned int GetFaceMaterialId(const AZ::SceneAPI::DataTypes::IBlendShapeData* meshData, unsigned int index); - static unsigned int GetFaceMaterialId(const AZ::SceneAPI::DataTypes::IMeshData* meshData, unsigned int index); - static void AddFace(AZ::SceneData::GraphData::BlendShapeData* blendShape, unsigned int index1, unsigned int index2, unsigned int index3, unsigned int faceMaterialId); static void AddFace(AZ::SceneData::GraphData::MeshData* mesh, unsigned int index1, unsigned int index2, unsigned int index3, unsigned int faceMaterialId); }; From 4485edf77d118771e4a051fefb91d009deb7353f Mon Sep 17 00:00:00 2001 From: Eric Phister <52085794+amzn-phist@users.noreply.github.com> Date: Mon, 3 May 2021 17:46:54 -0500 Subject: [PATCH 3/4] LYN-2578: Updates cmake install for 'scripts' directory. (#518) * LYN-2578: Updates cmake install for 'scripts' directory. Updates destination of certain binaries. * LYN-2578: Updates to cmake install based on feedback. --- CMakeLists.txt | 4 ++- cmake/Platform/Common/Install_common.cmake | 38 +++++++++++++++------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cefbcf2ef4..868757ebcd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,7 +75,9 @@ foreach(restricted_platform ${PAL_RESTRICTED_PLATFORMS}) endif() endforeach() -add_subdirectory(scripts) +if(NOT INSTALLED_ENGINE) + add_subdirectory(scripts) +endif() # SPEC-1417 will investigate and fix this if(NOT PAL_PLATFORM_NAME STREQUAL "Mac") diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index fb3a7b1b09..8fe2fe2c1c 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -286,7 +286,7 @@ endfunction() function(ly_setup_others) # List of directories we want to install relative to engine root - set(DIRECTORIES_TO_INSTALL Tools/LyTestTools Tools/RemoteConsole scripts) + set(DIRECTORIES_TO_INSTALL Tools/LyTestTools Tools/RemoteConsole) foreach(dir ${DIRECTORIES_TO_INSTALL}) get_filename_component(install_path ${dir} DIRECTORY) @@ -301,6 +301,24 @@ function(ly_setup_others) endforeach() + # Scripts + file(GLOB o3de_scripts "${CMAKE_SOURCE_DIR}/scripts/o3de.*") + install(FILES + ${o3de_scripts} + DESTINATION ./scripts + COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} + ) + + install(DIRECTORY + ${CMAKE_SOURCE_DIR}/scripts/bundler + ${CMAKE_SOURCE_DIR}/scripts/project_manager + DESTINATION ./scripts + COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} + PATTERN "__pycache__" EXCLUDE + PATTERN "CMakeLists.txt" EXCLUDE + PATTERN "tests" EXCLUDE + ) + install(DIRECTORY "${CMAKE_SOURCE_DIR}/python" DESTINATION . COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} @@ -311,7 +329,7 @@ function(ly_setup_others) # Registry install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin/$/Registry - DESTINATION ./bin/$ + DESTINATION ./bin/${PAL_PLATFORM_NAME}/$ COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} ) install(DIRECTORY @@ -350,16 +368,14 @@ function(ly_setup_others) endif() endforeach() - # Qt Binaries - set(QT_BIN_DIRS bearer iconengines imageformats platforms styles translations) - foreach(qt_dir ${QT_BIN_DIRS}) - install(DIRECTORY - ${CMAKE_CURRENT_BINARY_DIR}/bin/$/${qt_dir} - DESTINATION ./bin/$ - COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} - ) - endforeach() + set(QT_DIRS bearer iconengines imageformats platforms styles translations) + list(TRANSFORM QT_DIRS PREPEND "${CMAKE_CURRENT_BINARY_DIR}/bin/$/" OUTPUT_VARIABLE QT_BIN_DIRS) + install(DIRECTORY + ${QT_BIN_DIRS} + DESTINATION ./bin/${PAL_PLATFORM_NAME}/$ + COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} + ) # Templates install(DIRECTORY From dfe57c9d8f7a66c127ddcc511f831ed3eca5e47e Mon Sep 17 00:00:00 2001 From: Vincent Liu <5900509+onecent1101@users.noreply.github.com> Date: Mon, 3 May 2021 16:16:19 -0700 Subject: [PATCH 4/4] [LYN-2964][LYN-2965] Improve user experience of using AWSScriptBehaviorS3 (#495) As AWS S3 GetObject doesn't provide proper file handling, move logic into custom request validation step --- .../Public/ScriptCanvas/AWSScriptBehaviorS3.h | 12 +++- .../ScriptCanvas/AWSScriptBehaviorS3.cpp | 58 +++++++++++----- .../ScriptCanvas/AWSScriptBehaviorS3Test.cpp | 67 ++++++++++++++++++- 3 files changed, 119 insertions(+), 18 deletions(-) diff --git a/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorS3.h b/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorS3.h index 5d7baf19a1..9d779e3629 100644 --- a/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorS3.h +++ b/Gems/AWSCore/Code/Include/Public/ScriptCanvas/AWSScriptBehaviorS3.h @@ -75,6 +75,16 @@ namespace AWSCore class AWSScriptBehaviorS3 : public AWSScriptBehaviorBase { + static constexpr const char AWSScriptBehaviorS3Name[] = "AWSScriptBehaviorS3"; + static constexpr const char OutputFileIsEmptyErrorMessage[] = "Request validation failed, output file is empty."; + static constexpr const char OutputFileMissFullPathErrorMessage[] = "Request validation failed, output file miss full path."; + static constexpr const char OutputFileIsDirectoryErrorMessage[] = "Request validation failed, output file is a directory."; + static constexpr const char OutputFileDirectoryNotExistErrorMessage[] = "Request validation failed, output file directory doesn't exist."; + static constexpr const char OutputFileIsReadOnlyErrorMessage[] = "Request validation failed, output file is read-only."; + static constexpr const char BucketNameIsEmptyErrorMessage[] = "Request validation failed, bucket name is empty"; + static constexpr const char ObjectKeyNameIsEmptyErrorMessage[] = "Request validation failed, object key name is empty."; + static constexpr const char RegionNameIsEmptyErrorMessage[] = "Request validation failed, region name is empty."; + public: AWS_SCRIPT_BEHAVIOR_DEFINITION(AWSScriptBehaviorS3, "{7F4E956C-7463-4236-B320-C992D36A9C6E}"); @@ -87,7 +97,7 @@ namespace AWSCore private: using S3NotificationFunctionType = void(AWSScriptBehaviorS3Notifications::*)(const AZStd::string&); static bool ValidateGetObjectRequest(S3NotificationFunctionType notificationFunc, - const AZStd::string& bucket, const AZStd::string& objectKey, const AZStd::string& region, const AZStd::string& outFile); + const AZStd::string& bucket, const AZStd::string& objectKey, const AZStd::string& region, AZStd::string& outFile); static bool ValidateHeadObjectRequest(S3NotificationFunctionType notificationFunc, const AZStd::string& bucket, const AZStd::string& key, const AZStd::string& region); diff --git a/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorS3.cpp b/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorS3.cpp index 742a6826af..1f529572f1 100644 --- a/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorS3.cpp +++ b/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorS3.cpp @@ -43,7 +43,7 @@ namespace AWSCore void AWSScriptBehaviorS3::ReflectBehaviors(AZ::BehaviorContext* behaviorContext) { - behaviorContext->Class("AWSScriptBehaviorS3") + behaviorContext->Class(AWSScriptBehaviorS3Name) ->Attribute(AZ::Script::Attributes::Category, "AWSCore") ->Method("GetObject", &AWSScriptBehaviorS3::GetObject, {{{"Bucket Resource KeyName", "The resource key name of the bucket in resource mapping config file."}, @@ -86,7 +86,9 @@ namespace AWSCore void AWSScriptBehaviorS3::GetObjectRaw( const AZStd::string& bucket, const AZStd::string& objectKey, const AZStd::string& region, const AZStd::string& outFile) { - if (!ValidateGetObjectRequest(&AWSScriptBehaviorS3NotificationBus::Events::OnGetObjectError, bucket, objectKey, region, outFile)) + AZStd::string normalizedOutFile = outFile; + if (!ValidateGetObjectRequest( + &AWSScriptBehaviorS3NotificationBus::Events::OnGetObjectError, bucket, objectKey, region, normalizedOutFile)) { return; } @@ -112,10 +114,10 @@ namespace AWSCore job->request.SetBucket(Aws::String(bucket.c_str())); job->request.SetKey(Aws::String(objectKey.c_str())); - Aws::String outFileName(outFile.c_str()); + Aws::String outFileName(normalizedOutFile.c_str()); job->request.SetResponseStreamFactory([outFileName]() { return Aws::New( - "AWSScriptBehaviorS3", outFileName.c_str(), + AWSScriptBehaviorS3Name, outFileName.c_str(), std::ios_base::out | std::ios_base::in | std::ios_base::binary | std::ios_base::trunc); }); job->Start(); @@ -163,20 +165,44 @@ namespace AWSCore } bool AWSScriptBehaviorS3::ValidateGetObjectRequest(S3NotificationFunctionType notificationFunc, - const AZStd::string& bucket, const AZStd::string& objectKey, const AZStd::string& region, const AZStd::string& outFile) + const AZStd::string& bucket, const AZStd::string& objectKey, const AZStd::string& region, AZStd::string& outFile) { if (ValidateHeadObjectRequest(notificationFunc, bucket, objectKey, region)) { - if (!AzFramework::StringFunc::Path::IsValid(outFile.c_str())) + AzFramework::StringFunc::Path::Normalize(outFile); + if (outFile.empty()) { - AZ_Warning("AWSScriptBehaviorS3", false, "Request validation failed, outfile is not valid."); - AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, "Request validation failed, outfile is not valid."); + AZ_Warning(AWSScriptBehaviorS3Name, false, OutputFileIsEmptyErrorMessage); + AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, OutputFileIsEmptyErrorMessage); return false; } + if (!AzFramework::StringFunc::Path::HasDrive(outFile.c_str())) + { + AZ_Warning(AWSScriptBehaviorS3Name, false, OutputFileMissFullPathErrorMessage); + AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, OutputFileMissFullPathErrorMessage); + return false; + } + if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(outFile.c_str())) + { + AZ_Warning(AWSScriptBehaviorS3Name, false, OutputFileIsDirectoryErrorMessage); + AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, OutputFileIsDirectoryErrorMessage); + return false; + } + auto lastSeparator = outFile.find_last_of(AZ_CORRECT_FILESYSTEM_SEPARATOR); + if (lastSeparator != AZStd::string::npos) + { + auto parentPath = outFile.substr(0, lastSeparator); + if (!AZ::IO::FileIOBase::GetInstance()->Exists(parentPath.c_str())) + { + AZ_Warning(AWSScriptBehaviorS3Name, false, OutputFileDirectoryNotExistErrorMessage); + AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, OutputFileDirectoryNotExistErrorMessage); + return false; + } + } if (AZ::IO::FileIOBase::GetInstance()->IsReadOnly(outFile.c_str())) { - AZ_Warning("AWSScriptBehaviorS3", false, "Request validation failed, outfile is read-only."); - AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, "Request validation failed, outfile is read-only."); + AZ_Warning(AWSScriptBehaviorS3Name, false, OutputFileIsReadOnlyErrorMessage); + AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, OutputFileIsReadOnlyErrorMessage); return false; } return true; @@ -189,20 +215,20 @@ namespace AWSCore { if (bucket.empty()) { - AZ_Warning("AWSScriptBehaviorS3", false, "Request validation failed, bucket name is required."); - AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, "Request validation failed, bucket name is required."); + AZ_Warning(AWSScriptBehaviorS3Name, false, BucketNameIsEmptyErrorMessage); + AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, BucketNameIsEmptyErrorMessage); return false; } if (objectKey.empty()) { - AZ_Warning("AWSScriptBehaviorS3", false, "Request validation failed, object key name is required."); - AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, "Request validation failed, object key name is required."); + AZ_Warning(AWSScriptBehaviorS3Name, false, ObjectKeyNameIsEmptyErrorMessage); + AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, ObjectKeyNameIsEmptyErrorMessage); return false; } if (region.empty()) { - AZ_Warning("AWSScriptBehaviorS3", false, "Request validation failed, region name is required."); - AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, "Request validation failed, region name is required."); + AZ_Warning(AWSScriptBehaviorS3Name, false, RegionNameIsEmptyErrorMessage); + AWSScriptBehaviorS3NotificationBus::Broadcast(notificationFunc, RegionNameIsEmptyErrorMessage); return false; } return true; diff --git a/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorS3Test.cpp b/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorS3Test.cpp index fe258580e0..3489f09ac3 100644 --- a/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorS3Test.cpp +++ b/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorS3Test.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include @@ -38,7 +39,37 @@ public: MOCK_METHOD1(OnGetObjectError, void(const AZStd::string&)); }; -using AWSScriptBehaviorS3Test = UnitTest::ScopedAllocatorSetupFixture; +class AWSScriptBehaviorS3Test + : public AWSCoreFixture +{ +public: + void CreateReadOnlyTestFile(const AZStd::string& filePath) + { + AZ::IO::SystemFile file; + if (!file.Open( + filePath.c_str(), + AZ::IO::SystemFile::OpenMode::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY)) + { + AZ_Assert(false, "Failed to open test file at %s", filePath.c_str()); + } + AZStd::string testContent = "It is a test file"; + if (file.Write(testContent.c_str(), testContent.size()) != testContent.size()) + { + AZ_Assert(false, "Failed to write test file with content %s", testContent.c_str()); + } + file.Close(); + AZ_Assert(AZ::IO::SystemFile::SetWritable(filePath.c_str(), false), "Failed to mark test file as read-only"); + } + + void RemoveReadOnlyTestFile(const AZStd::string& filePath) + { + if (!filePath.empty()) + { + AZ_Assert(AZ::IO::SystemFile::SetWritable(filePath.c_str(), true), "Failed to mark test file as writeable"); + AZ_Assert(AZ::IO::SystemFile::Delete(filePath.c_str()), "Failed to delete test config file at %s", filePath.c_str()); + } + } +}; TEST_F(AWSScriptBehaviorS3Test, HeadObjectRaw_CallWithEmptyBucketName_InvokeOnError) { @@ -96,6 +127,40 @@ TEST_F(AWSScriptBehaviorS3Test, GetObjectRaw_CallWithEmptyOutfileName_InvokeOnEr AWSScriptBehaviorS3::GetObjectRaw("dummyBucket", "dummyObject", "dummyRegion", ""); } +TEST_F(AWSScriptBehaviorS3Test, GetObjectRaw_CallWithOutfileNameMissFullPath_InvokeOnError) +{ + AWSScriptBehaviorS3NotificationBusHandlerMock s3HandlerMock; + EXPECT_CALL(s3HandlerMock, OnGetObjectError(::testing::_)).Times(1); + AWSScriptBehaviorS3::GetObjectRaw("dummyBucket", "dummyObject", "dummyRegion", "dummyOut.txt"); +} + +TEST_F(AWSScriptBehaviorS3Test, GetObjectRaw_CallWithOutfileNameIsDirectory_InvokeOnError) +{ + AWSScriptBehaviorS3NotificationBusHandlerMock s3HandlerMock; + EXPECT_CALL(s3HandlerMock, OnGetObjectError(::testing::_)).Times(1); + AWSScriptBehaviorS3::GetObjectRaw("dummyBucket", "dummyObject", "dummyRegion", AZ::Test::GetCurrentExecutablePath()); +} + +TEST_F(AWSScriptBehaviorS3Test, GetObjectRaw_CallWithOutfileDirectoryNoExist_InvokeOnError) +{ + AWSScriptBehaviorS3NotificationBusHandlerMock s3HandlerMock; + EXPECT_CALL(s3HandlerMock, OnGetObjectError(::testing::_)).Times(1); + AZStd::string dummyDirectory = AZStd::string::format("%s/dummyDirectory/dummyOut.txt", AZ::Test::GetCurrentExecutablePath().c_str()); + AWSScriptBehaviorS3::GetObjectRaw("dummyBucket", "dummyObject", "dummyRegion", dummyDirectory); +} + +TEST_F(AWSScriptBehaviorS3Test, GetObjectRaw_CallWithOutfileIsReadOnly_InvokeOnError) +{ + AWSScriptBehaviorS3NotificationBusHandlerMock s3HandlerMock; + EXPECT_CALL(s3HandlerMock, OnGetObjectError(::testing::_)).Times(1); + AZStd::string randomTestFile = AZStd::string::format("%s/test%s.txt", + AZ::Test::GetCurrentExecutablePath().c_str(), AZ::Uuid::CreateRandom().ToString(false, false).c_str()); + AzFramework::StringFunc::Path::Normalize(randomTestFile); + CreateReadOnlyTestFile(randomTestFile); + AWSScriptBehaviorS3::GetObjectRaw("dummyBucket", "dummyObject", "dummyRegion", randomTestFile); + RemoveReadOnlyTestFile(randomTestFile); +} + TEST_F(AWSScriptBehaviorS3Test, GetObject_NoBucketNameInResourceMappingFound_InvokeOnError) { AWSScriptBehaviorS3NotificationBusHandlerMock s3HandlerMock;