Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,257 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Asset/AssetInternal/WeakAsset.h>
namespace UnitTest
{
using namespace AZ;
using namespace AZ::Data;
using AssetIdTest = AllocatorsFixture;
TEST_F(AssetIdTest, AssetId_PrintDecimalSubId_SubIdIsDecimal)
{
// Arbitrary GUID, sub ID picked that will be different in decimal and hexadecimal.
AssetId id("{A9F596D7-9913-4BA4-AD4E-7E477FB9B542}", 20);
AZStd::string asString = id.ToString<AZStd::string>(AZ::Data::AssetId::SubIdDisplayType::Decimal);
ASSERT_EQ(asString.size(), 41);
ASSERT_EQ(asString[39], '2');
ASSERT_EQ(asString[40], '0');
}
TEST_F(AssetIdTest, AssetId_PrintHexadecimalSubId_SubIdIsHex)
{
// Arbitrary GUID, sub ID picked that will be different in decimal and hexadecimal.
AssetId id("{A9F596D7-9913-4BA4-AD4E-7E477FB9B542}", 20);
AZStd::string asString = id.ToString<AZStd::string>(AZ::Data::AssetId::SubIdDisplayType::Hex);
ASSERT_EQ(asString.size(), 41);
ASSERT_EQ(asString[39], '1');
ASSERT_EQ(asString[40], '4');
}
TEST_F(AssetIdTest, AssetIdLessThanOperator_LHSEqualsRHS_ReturnsFalse)
{
AssetId left("{88888888-4444-4444-4444-CCCCCCCCCCCC}", 1);
AssetId right("{88888888-4444-4444-4444-CCCCCCCCCCCC}", 1);
ASSERT_FALSE(left < right);
}
TEST_F(AssetIdTest, AssetIdLessThanOperator_GuidsEqualLHSSubIdLessThanRHS_ReturnsTrue)
{
AssetId left("{EEEEEEEE-EEEE-BBBB-BBBB-CCCCCCCCCCCC}", 0);
AssetId right("{EEEEEEEE-EEEE-BBBB-BBBB-CCCCCCCCCCCC}", 1);
ASSERT_TRUE(left < right);
}
TEST_F(AssetIdTest, AssetIdLessThanOperator_GuidsEqualLHSSubIdGreaterThanRHS_ReturnsFalse)
{
AssetId left("{66666666-2222-4444-3333-CCCCCCCCCCCC}", 4);
AssetId right("{66666666-2222-4444-3333-CCCCCCCCCCCC}", 2);
ASSERT_FALSE(left < right);
}
TEST_F(AssetIdTest, AssetIdLessThanOperator_LHSGuidLessThanRHS_ReturnsTrue)
{
AssetId left("{00000000-4444-4444-4444-CCCCCCCCCCCC}", 1);
AssetId right("{10000000-4444-4444-4444-CCCCCCCCCCCC}", 1);
ASSERT_TRUE(left < right);
}
TEST_F(AssetIdTest, AssetIdLessThanOperator_LHSGuidGreaterThanRHS_ReturnsFalse)
{
AssetId left("{10200000-4444-4444-4444-CCCCCCCCCCCC}", 1);
AssetId right("{10000000-4444-4444-4444-CCCCCCCCCCCC}", 1);
ASSERT_FALSE(left < right);
}
using AssetTest = AllocatorsFixture;
TEST_F(AssetTest, AssetPreserveHintTest_Const_Copy)
{
// test to make sure that when we copy asset<T>s around using copy operations
// that the asset Hint is preserved in the case of assets being copied from things missing asset hints.
AssetId id("{52C79B55-B5AA-4841-AFC8-683D77716287}", 1);
AssetId idWithDifferentAssetId("{EA554205-D887-4A01-9E39-A318DDE4C0FC}", 1);
AssetType typeOfExample("{A99E8722-1F1D-4CA9-B89B-921EB3D907A9}");
Asset<AssetData> assetWithHint(id, typeOfExample, "an asset hint");
Asset<AssetData> differentAssetEntirely(idWithDifferentAssetId, typeOfExample, "");
Asset<AssetData> sameAssetWithoutHint(id, typeOfExample, "");
Asset<AssetData> sameAssetWithDifferentHint(id, typeOfExample, "a different hint");
// if we copy an asset from one with the same id, but no hint, preserve the sources hint.
assetWithHint = sameAssetWithoutHint;
ASSERT_STREQ(assetWithHint.GetHint().c_str(), "an asset hint");
// if we copy from an asset with same id, but with a different hint, we do not preserve the hint.
assetWithHint = sameAssetWithDifferentHint;
ASSERT_STREQ(assetWithHint.GetHint().c_str(), "a different hint");
// if we copy different assets (different id or sub) the hint must be copied.
// even if its empty.
assetWithHint = Asset<AssetData>(id, typeOfExample, "an asset hint");
assetWithHint = differentAssetEntirely;
ASSERT_STREQ(assetWithHint.GetHint().c_str(), "");
// ensure copy construction copies the hint.
Asset<AssetData> copied(sameAssetWithDifferentHint);
ASSERT_STREQ(copied.GetHint().c_str(), "a different hint");
}
TEST_F(AssetTest, AssetPreserveHintTest_Rvalue_Ref_Move)
{
// test to make sure that when we move asset<T>s around using move operators
// that the asset Hint is preserved in the case of assets being moved from things missing asset hints.
AssetId id("{52C79B55-B5AA-4841-AFC8-683D77716287}", 1);
AssetId idWithDifferentAssetId("{EA554205-D887-4A01-9E39-A318DDE4C0FC}", 1);
AssetType typeOfExample("{A99E8722-1F1D-4CA9-B89B-921EB3D907A9}");
Asset<AssetData> assetWithHint(id, typeOfExample, "an asset hint");
Asset<AssetData> differentAssetEntirely(idWithDifferentAssetId, typeOfExample, "");
Asset<AssetData> sameAssetWithoutHint(id, typeOfExample, "");
Asset<AssetData> sameAssetWithDifferentHint(id, typeOfExample, "a different hint");
// if we move an asset from one with the same id, but no hint, preserve the sources hint.
assetWithHint = AZStd::move(sameAssetWithoutHint);
ASSERT_STREQ(assetWithHint.GetHint().c_str(), "an asset hint");
// if we move from an asset with same id, but with a different hint, we do not preserve the hint.
assetWithHint = AZStd::move(sameAssetWithDifferentHint);
ASSERT_STREQ(assetWithHint.GetHint().c_str(), "a different hint");
// if we move different assets (different id or sub) the hint must be copied.
// even if its empty.
assetWithHint = Asset<AssetData>(id, typeOfExample, "an asset hint");
assetWithHint = AZStd::move(differentAssetEntirely);
ASSERT_STREQ(assetWithHint.GetHint().c_str(), "");
// ensure move construction copies the hint.
Asset<AssetData> copied(Asset<AssetData>(id, typeOfExample, "a different hint"));
ASSERT_STREQ(copied.GetHint().c_str(), "a different hint");
}
TEST_F(AllocatorsFixture, AssetReleaseRetainsAssetState_Id_Type_Hint)
{
const AssetId id("{52C79B55-B5AA-4841-AFC8-683D77716287}", 1);
const AssetType type("{A99E8722-1F1D-4CA9-B89B-921EB3D907A9}");
const AZStd::string hint("an asset hint");
Asset<AssetData> assetWithHint(id, type, hint);
assetWithHint.Release();
EXPECT_EQ(assetWithHint.GetId(), id);
EXPECT_EQ(assetWithHint.GetType(), type);
EXPECT_EQ(assetWithHint.GetHint(), hint);
}
TEST_F(AllocatorsFixture, AssetResetDefaultsAssetState_Id_Type_Hint)
{
const AssetId id("{52C79B55-B5AA-4841-AFC8-683D77716287}", 1);
const AssetType type("{A99E8722-1F1D-4CA9-B89B-921EB3D907A9}");
const AZStd::string hint("an asset hint");
Asset<AssetData> assetWithHint(id, type, hint);
assetWithHint.Reset();
EXPECT_EQ(assetWithHint.GetId(), AssetId());
EXPECT_EQ(assetWithHint.GetType(), AssetType::CreateNull());
EXPECT_EQ(assetWithHint.GetHint(), AZStd::string{});
}
using WeakAssetTest = AllocatorsFixture;
// Expose the internal weak use count of AssetData for verification in unit tests.
class TestAssetData : public AssetData
{
public:
static inline const AssetId ArbitraryAssetId{ "{E14BD18D-A933-4CBD-B64E-25F14D5E69E4}", 1 };
TestAssetData(const AssetId& assetId = ArbitraryAssetId) : AssetData(assetId) {}
int GetWeakUseCount() { return m_weakUseCount.load(); }
};
TEST_F(WeakAssetTest, WeakAsset_ConstructionAndDestruction_UpdatesAssetDataWeakRefCount)
{
TestAssetData testData;
EXPECT_EQ(testData.GetWeakUseCount(), 0);
// When transitioning the weak use count from 1 to 0, one assert will fire due to the asset manager not being initialized.
AZ_TEST_START_TRACE_SUPPRESSION;
{
AssetInternal::WeakAsset<TestAssetData> weakAsset(&testData, AssetLoadBehavior::Default);
EXPECT_EQ(testData.GetWeakUseCount(), 1);
}
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
EXPECT_EQ(testData.GetWeakUseCount(), 0);
}
TEST_F(WeakAssetTest, WeakAsset_MoveOperatorWithDifferentData_UpdatesOldAssetDataWeakRefCount)
{
TestAssetData testData;
EXPECT_EQ(testData.GetWeakUseCount(), 0);
AssetInternal::WeakAsset<TestAssetData> weakAsset(&testData, AssetLoadBehavior::Default);
EXPECT_EQ(testData.GetWeakUseCount(), 1);
// When transitioning the weak use count from 1 to 0, one assert will fire due to the asset manager not being initialized.
AZ_TEST_START_TRACE_SUPPRESSION;
weakAsset = {};
EXPECT_EQ(testData.GetWeakUseCount(), 0);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
TEST_F(WeakAssetTest, WeakAsset_MoveOperatorWithSameData_PreservesAssetDataWeakRefCount)
{
TestAssetData testData;
EXPECT_EQ(testData.GetWeakUseCount(), 0);
// When transitioning the weak use count from 1 to 0, one assert will fire due to the asset manager not being initialized.
AZ_TEST_START_TRACE_SUPPRESSION;
{
AssetInternal::WeakAsset<TestAssetData> weakAsset(&testData, AssetLoadBehavior::Default);
EXPECT_EQ(testData.GetWeakUseCount(), 1);
weakAsset = { &testData, AssetLoadBehavior::Default };
EXPECT_EQ(testData.GetWeakUseCount(), 1);
}
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
TEST_F(WeakAssetTest, WeakAsset_AssignmentOperator_CopiesDataAndIncrementsWeakRefCount)
{
TestAssetData testData;
EXPECT_EQ(testData.GetWeakUseCount(), 0);
// When transitioning the weak use count from 1 to 0, one assert will fire due to the asset manager not being initialized.
AZ_TEST_START_TRACE_SUPPRESSION;
{
const AssetInternal::WeakAsset<TestAssetData> weakAsset(&testData, AssetLoadBehavior::PreLoad);
EXPECT_EQ(testData.GetWeakUseCount(), 1);
AssetInternal::WeakAsset<TestAssetData> weakAsset2;
weakAsset2 = weakAsset;
EXPECT_EQ(testData.GetWeakUseCount(), 2);
EXPECT_EQ(weakAsset.GetId(), weakAsset2.GetId());
}
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
}
@@ -0,0 +1,501 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Asset/AssetDataStream.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AZTestShared/Utils/Utils.h>
#include <Tests/Streamer/IStreamerMock.h>
class AssetDataStreamTest
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
void SetUp() override
{
using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
UnitTest::ScopedAllocatorSetupFixture::SetUp();
AZ::Interface<AZ::IO::IStreamer>::Register(&m_mockStreamer);
// Reroute enough mock streamer calls to this class to let us validate the input parameters and mock
// out a "functioning" read request.
ON_CALL(m_mockStreamer, Read(_,::testing::An<IStreamerTypes::RequestMemoryAllocator&>(),_,_,_,_))
.WillByDefault([this](
AZStd::string_view relativePath,
IStreamerTypes::RequestMemoryAllocator& allocator,
size_t size,
AZStd::chrono::microseconds deadline,
IStreamerTypes::Priority priority,
size_t offset)
{
// Save off all the input parameters to the read request so that we can validate that they match expectations.
m_allocator = &allocator;
m_relativePath = relativePath;
m_size = size;
m_deadline = deadline;
m_priority = priority;
m_offset = offset;
return nullptr;
});
ON_CALL(m_mockStreamer, SetRequestCompleteCallback(_, _))
.WillByDefault([this](FileRequestPtr& request, AZ::IO::IStreamer::OnCompleteCallback callback) -> FileRequestPtr&
{
// Save off the callback just so that we can call it when the request is "done"
m_callback = callback;
return request;
});
ON_CALL(m_mockStreamer, QueueRequest(_))
.WillByDefault([this](const FileRequestPtr& request)
{
// As soon as the request is queued to run, consider it "done" and call the callback
if (m_callback)
{
FileRequestHandle handle(request);
m_callback(handle);
}
});
ON_CALL(m_mockStreamer, GetRequestStatus(_))
.WillByDefault([this]([[maybe_unused]] FileRequestHandle request)
{
// Return whatever request status has been set in this class
return m_requestStatus;
});
ON_CALL(m_mockStreamer, GetReadRequestResult(_, _, _, _))
.WillByDefault([this](
[[maybe_unused]] FileRequestHandle request,
void*& buffer,
AZ::u64& numBytesRead,
[[maybe_unused]] IStreamerTypes::ClaimMemory claimMemory)
{
auto result = m_allocator->Allocate(m_size, m_size, AZCORE_GLOBAL_NEW_ALIGNMENT);
m_buffer = static_cast<AZ::u8*>(result.m_address);
memset(m_buffer, m_expectedBufferChar, m_size);
numBytesRead = m_size;
buffer = m_buffer;
return true;
});
}
void TearDown() override
{
AZ::Interface<AZ::IO::IStreamer>::Unregister(&m_mockStreamer);
UnitTest::ScopedAllocatorSetupFixture::TearDown();
}
protected:
::testing::NiceMock<StreamerMock> m_mockStreamer;
AZStd::string_view m_relativePath;
size_t m_size{ 0 };
AZStd::chrono::microseconds m_deadline{ 0 };
IStreamerTypes::Priority m_priority{ IStreamerTypes::s_priorityLowest };
size_t m_offset{ 0 };
AZ::u8* m_buffer{ nullptr };
IStreamerTypes::RequestStatus m_requestStatus{ IStreamerTypes::RequestStatus::Completed };
AZ::IO::IStreamer::OnCompleteCallback m_callback{};
IStreamerTypes::RequestMemoryAllocator* m_allocator{ nullptr };
// Define some arbitrary numbers for validating buffer contents
static inline constexpr AZ::u8 m_expectedBufferChar{ 0xFE };
static inline constexpr AZ::u8 m_badBufferChar{ 0xFD };
};
TEST_F(AssetDataStreamTest, Init_CreateTrivialInstance_CreationSuccessful)
{
AZ::Data::AssetDataStream assetDataStream;
// AssetDataStream is a read-only stream
EXPECT_TRUE(assetDataStream.CanRead());
EXPECT_FALSE(assetDataStream.CanWrite());
// AssetDataStream only supports forward seeking, not arbitrary seeking, so CanSeek() should be false.
EXPECT_FALSE(assetDataStream.CanSeek());
}
TEST_F(AssetDataStreamTest, Open_OpenAndCopyBuffer_BufferCopied)
{
// Pick an arbitrary buffer size
constexpr int bufferSize = 100;
// Create a buffer filled with an expected charater
AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
// Create an assetDataStream and *copy* the buffer into it
AZ::Data::AssetDataStream assetDataStream;
assetDataStream.Open(buffer);
// Assign the buffer to a different, unexpected character
buffer.assign(bufferSize, m_badBufferChar);
// Create a new buffer, fill it with a bad character, and read the data from the assetDataStream into it
AZStd::vector<AZ::u8> outBuffer(bufferSize, m_badBufferChar);
AZ::IO::SizeType bytesRead = assetDataStream.Read(outBuffer.size(), outBuffer.data());
// Validate that we read the correct number of bytes
EXPECT_EQ(bytesRead, outBuffer.size());
// Validate that the data read back does *not* match the invalid data.
// This validates that the buffer got copied and not directly used.
EXPECT_NE(buffer, outBuffer);
// Validate that the data read back *does* match the original valid data.
buffer.assign(bufferSize, m_expectedBufferChar);
EXPECT_EQ(buffer, outBuffer);
assetDataStream.Close();
}
TEST_F(AssetDataStreamTest, Open_OpenAndUseBuffer_BufferUsed)
{
// Pick an arbitrary buffer size
constexpr int bufferSize = 100;
// Create a buffer filled with an expected charater
AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
// Create an assetDataStream and *move* the buffer into it
AZ::Data::AssetDataStream assetDataStream;
assetDataStream.Open(AZStd::move(buffer));
// The buffer should be moved, so it should no longer contain data.
EXPECT_TRUE(buffer.empty());
// Create a new buffer, fill it with a bad character, and read the data from the assetDataStream into it
AZStd::vector<AZ::u8> outBuffer(bufferSize, m_badBufferChar);
AZ::IO::SizeType bytesRead = assetDataStream.Read(outBuffer.size(), outBuffer.data());
// Validate that we read the correct number of bytes
EXPECT_EQ(bytesRead, outBuffer.size());
// Validate that the data read back matches the original valid data.
buffer.assign(bufferSize, m_expectedBufferChar);
EXPECT_EQ(buffer, outBuffer);
assetDataStream.Close();
}
TEST_F(AssetDataStreamTest, Open_OpenAndReadFile_FileReadSuccessfully)
{
// Choose some non-standard input values to pass in to our Open() request.
const AZStd::string filePath("path/test");
const size_t fileOffset = 100;
const size_t assetSize = 500;
const AZStd::chrono::milliseconds deadline(1000);
const AZ::IO::IStreamerTypes::Priority priority(AZ::IO::IStreamerTypes::s_priorityHigh);
// Keep track of whether or not our callback gets called.
bool callbackCalled = false;
AZ::IO::IStreamerTypes::RequestStatus callbackStatus;
// Create a callback to call on load completion.
AZ::Data::AssetDataStream::OnCompleteCallback loadCallback =
[&callbackCalled, &callbackStatus](AZ::IO::IStreamerTypes::RequestStatus status)
{
callbackCalled = true;
callbackStatus = status;
};
// Create an AssetDataStream, create a file open request, and wait for it to finish.
AZ::Data::AssetDataStream assetDataStream;
assetDataStream.Open(filePath, fileOffset, assetSize, deadline, priority, loadCallback);
assetDataStream.BlockUntilLoadComplete();
// Validate that the AssetDataStream passed our input parameters correctly to the file streamer
EXPECT_EQ(filePath, m_relativePath);
EXPECT_EQ(assetSize, m_size);
EXPECT_EQ(deadline, m_deadline);
EXPECT_EQ(priority, m_priority);
EXPECT_EQ(fileOffset, m_offset);
// Validate that our completion callback got called
EXPECT_TRUE(callbackCalled);
EXPECT_EQ(callbackStatus, m_requestStatus);
// Create a new buffer, fill it with a bad character, and read the data from the assetDataStream into it
AZStd::vector<AZ::u8> outBuffer(assetSize, m_badBufferChar);
AZ::IO::SizeType bytesRead = assetDataStream.Read(outBuffer.size(), outBuffer.data());
// Validate that we read the correct number of bytes
EXPECT_EQ(bytesRead, outBuffer.size());
// Validate that the data read back matches the original valid data.
EXPECT_EQ(memcmp(m_buffer, outBuffer.data(), bytesRead), 0);
assetDataStream.Close();
}
TEST_F(AssetDataStreamTest, IsOpen_OpenAndCloseStream_OnlyTrueWhileOpen)
{
// Pick an arbitrary buffer size
constexpr int bufferSize = 100;
// Create a buffer filled with an expected charater
AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
AZ::Data::AssetDataStream assetDataStream;
EXPECT_FALSE(assetDataStream.IsOpen());
assetDataStream.Open(AZStd::move(buffer));
EXPECT_TRUE(assetDataStream.IsOpen());
assetDataStream.Close();
EXPECT_FALSE(assetDataStream.IsOpen());
}
TEST_F(AssetDataStreamTest, IsFullyLoaded_OpenStreamFromBuffer_DataIsFullyLoaded)
{
// Pick an arbitrary buffer size
constexpr int bufferSize = 100;
// Create a buffer filled with an expected charater
AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
AZ::Data::AssetDataStream assetDataStream;
EXPECT_FALSE(assetDataStream.IsFullyLoaded());
EXPECT_EQ(assetDataStream.GetLoadedSize(), 0);
EXPECT_EQ(assetDataStream.GetLength(), 0);
assetDataStream.Open(AZStd::move(buffer));
EXPECT_TRUE(assetDataStream.IsFullyLoaded());
EXPECT_EQ(assetDataStream.GetLoadedSize(), bufferSize);
EXPECT_EQ(assetDataStream.GetLength(), bufferSize);
assetDataStream.Close();
EXPECT_FALSE(assetDataStream.IsFullyLoaded());
EXPECT_EQ(assetDataStream.GetLoadedSize(), 0);
EXPECT_EQ(assetDataStream.GetLength(), 0);
}
TEST_F(AssetDataStreamTest, IsFullyLoaded_FileDoesNotReadAllData_DataIsNotFullyLoaded)
{
// Set up some arbitrary file parameters.
const AZStd::string filePath("path/test");
const size_t fileOffset = 0;
const size_t assetSize = 500;
// Pick a size less than assetSize to represent the amount of data actually loaded
const size_t incompleteAssetSize = 200;
using ::testing::_;
ON_CALL(m_mockStreamer, GetReadRequestResult(_, _, _, _))
.WillByDefault([this, incompleteAssetSize](
[[maybe_unused]] FileRequestHandle request,
void*& buffer,
AZ::u64& numBytesRead,
[[maybe_unused]] IStreamerTypes::ClaimMemory claimMemory)
{
// On the request for the read result, create a size and buffer that's less than the requested amount.
m_size = incompleteAssetSize;
auto result = m_allocator->Allocate(m_size, m_size, AZCORE_GLOBAL_NEW_ALIGNMENT);
m_buffer = static_cast<AZ::u8*>(result.m_address);
memset(m_buffer, m_expectedBufferChar, m_size);
numBytesRead = m_size;
buffer = m_buffer;
return true;
});
// Create an AssetDataStream, create a file open request, and wait for it to finish.
AZ::Data::AssetDataStream assetDataStream;
// We expect one error message during the load due to the incomplete file load.
AZ_TEST_START_TRACE_SUPPRESSION;
assetDataStream.Open(filePath, fileOffset, assetSize);
assetDataStream.BlockUntilLoadComplete();
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
// Verify that the data reports back the incomplete size for loaded size, and that it is not fully loaded.
EXPECT_FALSE(assetDataStream.IsFullyLoaded());
EXPECT_EQ(assetDataStream.GetLoadedSize(), incompleteAssetSize);
// GetLength should still report back the expected size instead of the loaded size
EXPECT_EQ(assetDataStream.GetLength(), assetSize);
assetDataStream.Close();
}
TEST_F(AssetDataStreamTest, Write_TryWritingToStream_WritingCausesAsserts)
{
// Create an arbitrary buffer
constexpr int bufferSize = 100;
AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
// Create a data stream from the buffer
AZ::Data::AssetDataStream assetDataStream;
assetDataStream.Open(buffer);
// We should get an error when trying to write to the stream.
AZ_TEST_START_ASSERTTEST;
assetDataStream.Write(bufferSize, buffer.data());
AZ_TEST_STOP_ASSERTTEST(1);
assetDataStream.Close();
}
TEST_F(AssetDataStreamTest, GetFilename_StreamOpenedWithFile_FileNameMatches)
{
// Set up some arbitrary file parameters.
const AZStd::string filePath("path/test");
const size_t fileOffset = 0;
const size_t assetSize = 500;
// Create an AssetDataStream, create a file open request, and wait for it to finish.
AZ::Data::AssetDataStream assetDataStream;
assetDataStream.Open(filePath, fileOffset, assetSize);
assetDataStream.BlockUntilLoadComplete();
// Verify that the stream has the expected filename
EXPECT_EQ(strcmp(assetDataStream.GetFilename(), filePath.c_str()), 0);
assetDataStream.Close();
}
TEST_F(AssetDataStreamTest, GetFilename_StreamOpenedWithMemoryBuffer_FileNameIsEmpty)
{
// Create an arbitrary buffer
constexpr int bufferSize = 100;
AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
// Create an AssetDataStream from the memory buffer
AZ::Data::AssetDataStream assetDataStream;
assetDataStream.Open(buffer);
// Verify that the stream has no filename
EXPECT_EQ(strcmp(assetDataStream.GetFilename(), ""), 0);
assetDataStream.Close();
}
TEST_F(AssetDataStreamTest, BlockUntilLoadComplete_BlockWhenOpenedWithMemoryBuffer_BlockReturnsSuccessfully)
{
// Create an arbitrary buffer
constexpr int bufferSize = 100;
AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
// Create an AssetDataStream from the memory buffer
AZ::Data::AssetDataStream assetDataStream;
assetDataStream.Open(buffer);
// Verify that calling BlockUntilLoadComplete doesn't cause problems when used with a memory buffer instead of a file.
assetDataStream.BlockUntilLoadComplete();
assetDataStream.Close();
}
TEST_F(AssetDataStreamTest, Read_ReadDataIncrementally_PartialDataReadSuccessfully)
{
// Create an arbitrary buffer with different data in every byte
constexpr int bufferSize = 256;
AZStd::vector<AZ::u8> buffer(bufferSize);
for (int offset = 0; offset < bufferSize; offset++)
{
// Use the lowest 8 bits of offset to get a repeating pattern of 00 -> FF
buffer[offset] = offset & 0xFF;
}
// Create an AssetDataStream from the memory buffer
AZ::Data::AssetDataStream assetDataStream;
assetDataStream.Open(buffer);
for (int offset = 0; offset < bufferSize; offset++)
{
// Verify that the current position increments correctly on each read request
EXPECT_EQ(offset, assetDataStream.GetCurPos());
AZ::u8 byte;
auto bytesRead = assetDataStream.Read(1, &byte);
// Verify that when we read one byte at a time, it's incrementing forward through the data set and getting the correct byte.
EXPECT_EQ(bytesRead, 1);
EXPECT_EQ(byte, buffer[offset]);
}
assetDataStream.Close();
}
TEST_F(AssetDataStreamTest, Seek_SeekForward_SeekingForwardWorksSuccessfully)
{
// Create an arbitrary buffer with different data in every byte
constexpr int bufferSize = 256;
AZStd::vector<AZ::u8> buffer(bufferSize);
for (int offset = 0; offset < bufferSize; offset++)
{
// Use the lowest 8 bits of offset to get a repeating pattern of 00 -> FF
buffer[offset] = offset & 0xFF;
}
// Create an AssetDataStream from the memory buffer
AZ::Data::AssetDataStream assetDataStream;
assetDataStream.Open(buffer);
// Pick an arbitrary amount to seek forward on every iteration
constexpr int skipForwardBytes = 3;
// The expected offset should increment by the byte read plus the seek on every iteration
constexpr int offsetIncrement = skipForwardBytes + 1;
for (int offset = 0; offset < bufferSize; offset+=offsetIncrement)
{
// Verify that the current position is correct on each read request, even with the seek
EXPECT_EQ(offset, assetDataStream.GetCurPos());
AZ::u8 byte;
assetDataStream.Read(1, &byte);
// Verify that we're getting the byte we expected even with the seek
EXPECT_EQ(byte, buffer[offset]);
assetDataStream.Seek(skipForwardBytes, AZ::IO::GenericStream::SeekMode::ST_SEEK_CUR);
}
assetDataStream.Close();
}
TEST_F(AssetDataStreamTest, Seek_SeekBackward_SeekingBackwardNotAllowed)
{
// Create an arbitrary buffer
constexpr int bufferSize = 100;
AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
// Create an AssetDataStream from the memory buffer
AZ::Data::AssetDataStream assetDataStream;
assetDataStream.Open(buffer);
// Moving to the start of the file doesn't move, so this should succeed
assetDataStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
// Read a byte
AZ::u8 byte;
assetDataStream.Read(1, &byte);
// Moving to the start of the file now is moving backwards, so this should fail
AZ_TEST_START_ASSERTTEST;
assetDataStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
AZ_TEST_STOP_ASSERTTEST(1);
assetDataStream.Close();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,333 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/Streamer/Streamer.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Jobs/JobManager.h>
#include <AzCore/Jobs/JobContext.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/parallel/condition_variable.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AZTestShared/Utils/Utils.h>
#include <Streamer/IStreamerMock.h>
#include <Tests/Asset/BaseAssetManagerTest.h>
#include <Tests/Asset/MockLoadAssetCatalogAndHandler.h>
#include <Tests/Asset/TestAssetTypes.h>
#include <Tests/SerializeContextFixture.h>
#include <Tests/TestCatalog.h>
namespace UnitTest
{
using namespace AZ;
using namespace AZ::Data;
struct StreamerWrapper
{
StreamerWrapper()
{
using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
ON_CALL(m_mockStreamer, Read(_, ::testing::An<IStreamerTypes::RequestMemoryAllocator&>(), _, _, _, _))
.WillByDefault([this](
[[maybe_unused]] AZStd::string_view relativePath,
IStreamerTypes::RequestMemoryAllocator& allocator,
size_t size,
AZStd::chrono::microseconds deadline,
IStreamerTypes::Priority priority,
[[maybe_unused]] size_t offset)
{
// Save off the requested deadline and priority
m_deadline = deadline;
m_priority = priority;
// Allocate a real data buffer for the supposedly read-in asset
m_data = allocator.Allocate(size, size, 8);
// Create a real file request result and return it
m_request = m_context.GetNewExternalRequest();
return m_request;
});
ON_CALL(m_mockStreamer, SetRequestCompleteCallback(_, _))
.WillByDefault([this](FileRequestPtr& request, AZ::IO::IStreamer::OnCompleteCallback callback) -> FileRequestPtr&
{
// Save off the callback just so that we can call it when the request is "done"
m_callback = callback;
return request;
});
ON_CALL(m_mockStreamer, GetRequestStatus(_))
.WillByDefault([this]([[maybe_unused]] FileRequestHandle request)
{
// Return whatever request status has been set in this class
return IO::IStreamerTypes::RequestStatus::Completed;
});
ON_CALL(m_mockStreamer, GetReadRequestResult(_, _, _, _))
.WillByDefault([this](
[[maybe_unused]] FileRequestHandle request,
void*& buffer,
AZ::u64& numBytesRead,
IStreamerTypes::ClaimMemory claimMemory)
{
// Make sure the requestor plans to free the data buffer we allocated.
EXPECT_EQ(claimMemory, IStreamerTypes::ClaimMemory::Yes);
// Provide valid data buffer results.
numBytesRead = m_data.m_size;
buffer = m_data.m_address;
// Clear out our stored values for this, because we're handing off ownership to the caller.
m_data.m_address = nullptr;
m_data.m_size = 0;
return true;
});
ON_CALL(m_mockStreamer, RescheduleRequest(_, _, _))
.WillByDefault([this](IO::FileRequestPtr target, AZStd::chrono::microseconds newDeadline, IO::IStreamerTypes::Priority newPriority)
{
m_deadline = newDeadline;
m_priority = newPriority;
return target;
});
}
~StreamerWrapper() = default;
::testing::NiceMock<StreamerMock> m_mockStreamer;
AZStd::chrono::milliseconds m_deadline;
AZ::IO::IStreamerTypes::Priority m_priority;
IO::StreamerContext m_context;
AZ::IO::IStreamer::OnCompleteCallback m_callback;
IO::FileRequestPtr m_request;
IO::IStreamerTypes::RequestMemoryAllocatorResult m_data{ nullptr, 0, IO::IStreamerTypes::MemoryType::ReadWrite };
};
// Use a mock asset catalog and asset handler to pretend to create and load an asset, since we don't really care about the data
// inside the asset itself for these tests.
// This subclass overrides the asset information to provide non-zero asset sizes so that the asset load makes it all the way to the
// mocked-out streamer class, so that we can track information about streamer deadline and priority changes.
// This subclass also provides facilities for getting/setting the default deadline and priority so that we can test the usage of
// those values as well.
class MockLoadAssetWithNonZeroSizeCatalogAndHandler
: public MockLoadAssetCatalogAndHandler
{
public:
MockLoadAssetWithNonZeroSizeCatalogAndHandler(
AZStd::unordered_set<AZ::Data::AssetId> ids
, AZ::Data::AssetType assetType
, AZ::Data::AssetPtr(*createAsset)()
, void(*destroyAsset)(AZ::Data::AssetPtr asset))
: MockLoadAssetCatalogAndHandler(ids, assetType, createAsset, destroyAsset)
{
}
// Overridden to provide a non-zero asset size so that the asset load makes it to the streamer.
AZ::Data::AssetInfo GetAssetInfoById(const AZ::Data::AssetId& id) override
{
AZ::Data::AssetInfo result;
if (m_ids.contains(id))
{
result.m_assetType = m_assetType;
result.m_assetId = id;
result.m_sizeBytes = sizeof(EmptyAsset);
}
return result;
}
// Overridden to provide a non-zero size and non-empty name so that the asset load makes it to the streamer.
AZ::Data::AssetStreamInfo GetStreamInfoForLoad([[maybe_unused]] const AZ::Data::AssetId& id,
[[maybe_unused]] const AZ::Data::AssetType& type) override
{
AZ::Data::AssetStreamInfo info;
info.m_dataLen = sizeof(EmptyAsset);
info.m_streamFlags = AZ::IO::OpenMode::ModeRead;
info.m_streamName = "test";
return info;
}
// Provides controllable default values for deadlines and priorities.
void GetDefaultAssetLoadPriority([[maybe_unused]] AssetType type, AZStd::chrono::milliseconds& defaultDeadline,
AZ::IO::IStreamerTypes::Priority& defaultPriority) const override
{
defaultDeadline = GetDefaultDeadline();
defaultPriority = GetDefaultPriority();
}
AZStd::chrono::milliseconds GetDefaultDeadline() const { return m_defaultDeadline; }
AZ::IO::IStreamerTypes::Priority GetDefaultPriority() const { return m_defaultPriority; }
void SetDefaultDeadline(AZStd::chrono::milliseconds deadline) { m_defaultDeadline = deadline; }
void SetDefaultPriority(AZ::IO::IStreamerTypes::Priority priority) { m_defaultPriority = priority; }
protected:
AZStd::chrono::milliseconds m_defaultDeadline{ AZStd::chrono::milliseconds(0) };
AZ::IO::IStreamerTypes::Priority m_defaultPriority{ AZ::IO::IStreamerTypes::s_priorityLowest };
};
// Tests that validate the interaction between AssetManager and the IO Streamer
struct AssetManagerStreamerTests
: BaseAssetManagerTest
{
static inline const AZ::Uuid MyAsset1Id{ "{5B29FE2B-6B41-48C9-826A-C723951B0560}" };
void SetUp() override
{
BaseAssetManagerTest::SetUp();
// create the database
AssetManager::Descriptor desc;
AssetManager::Create(desc);
}
void TearDown() override
{
// This will also delete m_assetHandlerAndCatalog
AssetManager::Destroy();
BaseAssetManagerTest::TearDown();
}
size_t GetNumJobManagerThreads() const override
{
return 1;
}
// Create a mock streamer instead of a real one.
IO::IStreamer* CreateStreamer() override
{
m_mockStreamer = AZStd::make_unique<StreamerWrapper>();
return &(m_mockStreamer->m_mockStreamer);
}
void DestroyStreamer([[maybe_unused]] IO::IStreamer* streamer) override
{
m_mockStreamer = nullptr;
}
AZStd::unique_ptr<StreamerWrapper> m_mockStreamer;
};
TEST_F(AssetManagerStreamerTests, LoadReschedule)
{
struct DeadlinePriorityTest
{
// The deadline / priority values to request for this test
AZStd::chrono::milliseconds m_requestDeadline;
AZ::IO::IStreamerTypes::Priority m_requestPriority;
// The expected value of the deadline / priority after the request
AZStd::chrono::milliseconds m_resultDeadline;
AZ::IO::IStreamerTypes::Priority m_resultPriority;
};
DeadlinePriorityTest tests[] =
{
// Initial asset request with deadline and priority.
// Results should match what was requested.
{AZStd::chrono::milliseconds(1000), AZ::IO::IStreamerTypes::s_priorityLow,
AZStd::chrono::milliseconds(1000), AZ::IO::IStreamerTypes::s_priorityLow},
// Make the deadline longer and the priority higher.
// Only the priority should change.
{AZStd::chrono::milliseconds(1500), AZ::IO::IStreamerTypes::s_priorityHigh,
AZStd::chrono::milliseconds(1000), AZ::IO::IStreamerTypes::s_priorityHigh},
// Make the deadline shorter and the priority lower.
// Only the deadline should change.
{AZStd::chrono::milliseconds(500), AZ::IO::IStreamerTypes::s_priorityLow,
AZStd::chrono::milliseconds(500), AZ::IO::IStreamerTypes::s_priorityHigh},
// Make the deadline shorter and the priority higher.
// Both the deadline and the priority should change.
{AZStd::chrono::milliseconds(250), AZ::IO::IStreamerTypes::s_priorityHigh + 1,
AZStd::chrono::milliseconds(250), AZ::IO::IStreamerTypes::s_priorityHigh + 1},
};
// The deadline needs to be shorter than the last test scenario, and the priority higher, so that we can
// verify that these values are actually used in our final test scenario.
AZStd::chrono::milliseconds assetHandlerDefaultDeadline = AZStd::chrono::milliseconds(200);
AZ::IO::IStreamerTypes::Priority assetHandlerDefaultPriority = AZ::IO::IStreamerTypes::s_priorityHigh + 2;
UnitTest::MockLoadAssetWithNonZeroSizeCatalogAndHandler testAssetCatalog(
{ MyAsset1Id },
azrtti_typeid<EmptyAsset>(),
[]() { return AssetPtr(aznew EmptyAsset()); },
[](AssetPtr ptr) { delete ptr; }
);
{
AssetLoadParameters loadParams;
AZ::Data::Asset<EmptyAsset> asset1;
// Run through every test scenario and verify that the results match expectations.
for (auto& test : tests)
{
loadParams.m_deadline = test.m_requestDeadline;
loadParams.m_priority = test.m_requestPriority;
asset1 = AssetManager::Instance().GetAsset<EmptyAsset>(MyAsset1Id, AZ::Data::AssetLoadBehavior::Default, loadParams);
ASSERT_TRUE(asset1);
EXPECT_EQ(m_mockStreamer->m_deadline, test.m_resultDeadline);
EXPECT_EQ(m_mockStreamer->m_priority, test.m_resultPriority);
}
// Final scenario: Request another load with no deadline or priority set.
// This should use the defaults from the asset handler.
loadParams.m_deadline = {};
loadParams.m_priority = {};
testAssetCatalog.SetDefaultDeadline(assetHandlerDefaultDeadline);
testAssetCatalog.SetDefaultPriority(assetHandlerDefaultPriority);
// (Verify that we've chosen a shorter deadline and higher priority for our defaults than our current state)
EXPECT_LT(assetHandlerDefaultDeadline, m_mockStreamer->m_deadline);
EXPECT_GT(assetHandlerDefaultPriority, m_mockStreamer->m_priority);
asset1 = AssetManager::Instance().GetAsset<EmptyAsset>(MyAsset1Id, AZ::Data::AssetLoadBehavior::Default, loadParams);
ASSERT_TRUE(asset1);
EXPECT_EQ(m_mockStreamer->m_deadline, assetHandlerDefaultDeadline);
EXPECT_EQ(m_mockStreamer->m_priority, assetHandlerDefaultPriority);
// Run callback to cleanup and wait for the Asset Manager to finish processing the loaded asset.
m_mockStreamer->m_callback(m_mockStreamer->m_request);
asset1.BlockUntilLoadComplete();
// Allow the asset manager to finish processing the "OnAssetReady" event so that it doesn't hold extra
// references to the asset. This allows the asset to get cleaned up correctly at the end of the test.
AssetManager::Instance().DispatchEvents();
// Clear out our pointers so that they clean themselves up and release any references to the loading asset.
// If we didn't do this, the asset might not get cleaned up until the mock streamer is deleted, which happens
// after the asset manager shuts down. This would cause asserts and potentially a crash.
m_mockStreamer->m_callback = nullptr;
m_mockStreamer->m_request = nullptr;
}
}
}
@@ -0,0 +1,156 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Tests/Asset/BaseAssetManagerTest.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/Streamer/Streamer.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Jobs/JobManager.h>
#include <AzCore/Jobs/JobContext.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/parallel/condition_variable.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AZTestShared/Utils/Utils.h>
#include <Streamer/IStreamerMock.h>
namespace UnitTest
{
using namespace AZ;
using namespace AZ::Data;
/**
* Find the current status of the reload
*/
AZ::Data::AssetData::AssetStatus TestAssetManager::GetReloadStatus(const AssetId& assetId)
{
AZStd::lock_guard<AZStd::recursive_mutex> assetLock(m_assetMutex);
auto reloadInfo = m_reloads.find(assetId);
if (reloadInfo != m_reloads.end())
{
return reloadInfo->second.GetStatus();
}
return AZ::Data::AssetData::AssetStatus::NotLoaded;
}
size_t TestAssetManager::GetRemainingJobs() const
{
return m_activeJobs.size();
}
const AZ::Data::AssetManager::OwnedAssetContainerMap& TestAssetManager::GetAssetContainers() const
{
return m_ownedAssetContainers;
}
const AssetManager::AssetMap& TestAssetManager::GetAssets() const
{
return m_assets;
}
void BaseAssetManagerTest::SetUp()
{
SerializeContextFixture::SetUp();
AZ::JobManagerDesc jobDesc;
AZ::JobManagerThreadDesc threadDesc;
for (size_t threadCount = 0; threadCount < GetNumJobManagerThreads(); threadCount++)
{
jobDesc.m_workerThreads.push_back(threadDesc);
}
m_jobManager = aznew AZ::JobManager(jobDesc);
m_jobContext = aznew AZ::JobContext(*m_jobManager);
AZ::JobContext::SetGlobalContext(m_jobContext);
m_prevFileIO = IO::FileIOBase::GetInstance();
IO::FileIOBase::SetInstance(&m_fileIO);
m_streamer = CreateStreamer();
if (m_streamer)
{
Interface<IO::IStreamer>::Register(m_streamer);
}
}
void BaseAssetManagerTest::TearDown()
{
if (m_streamer)
{
Interface<IO::IStreamer>::Unregister(m_streamer);
}
DestroyStreamer(m_streamer);
// Clean up any temporary asset files created during the test.
for (auto& assetName : m_assetsWritten)
{
DeleteAssetFromDisk(assetName);
}
// Make sure to clear the memory from the name storage before shutting down the allocator.
m_assetsWritten.clear();
m_assetsWritten.shrink_to_fit();
IO::FileIOBase::SetInstance(m_prevFileIO);
AZ::JobContext::SetGlobalContext(nullptr);
delete m_jobContext;
delete m_jobManager;
SerializeContextFixture::TearDown();
}
void BaseAssetManagerTest::WriteAssetToDisk(const AZStd::string& assetName, [[maybe_unused]] const AZStd::string& assetIdGuid)
{
AZStd::string assetFileName = GetTestFolderPath() + assetName;
AssetWithCustomData asset;
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(assetFileName, AZ::DataStream::ST_XML, &asset, m_serializeContext));
// Keep track of every asset written so that we can remove it on teardown
m_assetsWritten.emplace_back(AZStd::move(assetFileName));
}
void BaseAssetManagerTest::DeleteAssetFromDisk(const AZStd::string& assetName)
{
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
if (fileIO->Exists(assetName.c_str()))
{
fileIO->Remove(assetName.c_str());
}
}
void BaseAssetManagerTest::BlockUntilAssetJobsAreComplete()
{
auto maxTimeout = AZStd::chrono::system_clock::now() + DefaultTimeoutSeconds;
while (AssetManager::Instance().HasActiveJobsOrStreamerRequests())
{
if (AZStd::chrono::system_clock::now() > maxTimeout)
{
break;
}
AZStd::this_thread::yield();
}
EXPECT_FALSE(AssetManager::Instance().HasActiveJobsOrStreamerRequests());
}
}
@@ -0,0 +1,87 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/IO/Streamer/Streamer.h>
#include <AzCore/IO/Streamer/StreamerComponent.h>
#include <AzCore/Jobs/JobManager.h>
#include <AzCore/Jobs/JobContext.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AZTestShared/Utils/Utils.h>
#include <Tests/FileIOBaseTestTypes.h>
#include <Tests/Asset/TestAssetTypes.h>
#include <Tests/SerializeContextFixture.h>
#include <Tests/TestCatalog.h>
namespace UnitTest
{
// Helper subclass of AssetManager that makes it possible to query some of the normally hidden implementation.
class TestAssetManager : public AssetManager
{
public:
TestAssetManager(const Descriptor& desc) : AssetManager(desc) {}
// Find the current status of the reload
AZ::Data::AssetData::AssetStatus GetReloadStatus(const AssetId& assetId);
// Get the number of jobs left to process
size_t GetRemainingJobs() const;
const AZ::Data::AssetManager::OwnedAssetContainerMap& GetAssetContainers() const;
const AssetMap& GetAssets() const;
// Expose these methods so that they can be queried by the unit tests.
using AssetManager::GetAssetInternal;
using AssetManager::GetAssetContainer;
};
// Base test class for the AssetManager unit tests that provides the general setup/teardown needed for all the tests.
class BaseAssetManagerTest
: public SerializeContextFixture
{
public:
~BaseAssetManagerTest() override = default;
// Subclasses are required to declare how many job threads they would like for their tests.
virtual size_t GetNumJobManagerThreads() const = 0;
// Subclasses can optionally override the streamer creation and destruction
virtual IO::IStreamer* CreateStreamer() { return aznew IO::Streamer(AZStd::thread_desc{}, StreamerComponent::CreateStreamerStack()); }
virtual void DestroyStreamer(IO::IStreamer* streamer) { delete streamer; }
void SetUp() override;
void TearDown() override;
// Helper methods to create and destroy actual assets on the disk for true end-to-end asset loading.
void WriteAssetToDisk(const AZStd::string& assetName, const AZStd::string& assetIdGuid);
void DeleteAssetFromDisk(const AZStd::string& assetName);
void BlockUntilAssetJobsAreComplete();
constexpr static AZStd::chrono::duration<AZStd::sys_time_t> DefaultTimeoutSeconds{ AZStd::chrono::seconds(AZ_TRAIT_UNIT_TEST_ASSET_MANAGER_TEST_DEFAULT_TIMEOUT_SECS) };
protected:
AZ::JobManager* m_jobManager{ nullptr };
AZ::JobContext* m_jobContext{ nullptr };
IO::FileIOBase* m_prevFileIO{ nullptr };
IO::IStreamer* m_streamer{ nullptr };
TestFileIOBase m_fileIO;
AZStd::vector<AZStd::string> m_assetsWritten;
};
}
@@ -0,0 +1,116 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetManager.h>
#include <Tests/Asset/TestAssetTypes.h>
namespace UnitTest
{
/**
* MockLoadAssetCatalogAndHandler is a mock AssetCatalog / AssetHandler that will take a set of Asset IDs
* and pretend to load them as valid assets of the given type. This is useful for unit tests that need to test functionality
* like setting asset IDs or loading assets that may trigger a dependent asset load as a side effect, but the actual asset
* loaded is irrelevant to the test.
* To use: Simply create an instance of this in the unit test, and pass the set of asset IDs to mock out into the constructor.
*/
class MockLoadAssetCatalogAndHandler
: public AZ::Data::AssetCatalog
, public AZ::Data::AssetCatalogRequestBus::Handler
, public AZ::Data::AssetHandler
{
public:
AZ_CLASS_ALLOCATOR(MockLoadAssetCatalogAndHandler, AZ::SystemAllocator, 0);
/**
* Create the mock AssetCatalog / AssetHandler
* @param ids The set of asset IDs to fake load. Ex: {id1, id2, ...}
* @param assetType (optional) The asset type to register as and use. Defaults to EmptyAsset.
* @param createAsset (optional) A lambda function for constructing a new asset of this type. Defaults to no-op.
* @param destroyAsset (optional) A lambda function for destroying the constructed asset. Defaults to no-op.
*/
MockLoadAssetCatalogAndHandler(
AZStd::unordered_set<AZ::Data::AssetId> ids
, AZ::Data::AssetType assetType = azrtti_typeid<UnitTest::EmptyAsset>()
, AZ::Data::AssetPtr(*createAsset)() = []() { return AZ::Data::AssetPtr(nullptr); }
, void(*destroyAsset)(AZ::Data::AssetPtr asset) = [](AZ::Data::AssetPtr) {})
: m_ids(AZStd::move(ids))
, m_assetType(assetType)
, m_createAsset(createAsset)
, m_destroyAsset(destroyAsset)
{
AZ::Data::AssetManager::Instance().RegisterHandler(this, m_assetType);
AZ::Data::AssetManager::Instance().RegisterCatalog(this, m_assetType);
AZ::Data::AssetCatalogRequestBus::Handler::BusConnect();
}
~MockLoadAssetCatalogAndHandler()
{
AZ::Data::AssetCatalogRequestBus::Handler::BusDisconnect();
AZ::Data::AssetManager::Instance().UnregisterCatalog(this);
AZ::Data::AssetManager::Instance().UnregisterHandler(this);
}
//////////////////////////////////////////////////////////////////////////
// AssetCatalogRequestBus
AZ::Data::AssetInfo GetAssetInfoById(const AZ::Data::AssetId& id) override
{
AZ::Data::AssetInfo result;
if (m_ids.contains(id))
{
result.m_assetType = m_assetType;
result.m_assetId = id;
}
return result;
}
//////////////////////////////////////////////////////////////////////////
// AssetCatalog
AZ::Data::AssetStreamInfo GetStreamInfoForLoad([[maybe_unused]] const AZ::Data::AssetId& id,
[[maybe_unused]] const AZ::Data::AssetType& type) override
{
AZ::Data::AssetStreamInfo info;
return info;
}
//////////////////////////////////////////////////////////////////////////
// AssetHandler
AZ::Data::AssetPtr CreateAsset([[maybe_unused]] const AZ::Data::AssetId& id,
[[maybe_unused]] const AZ::Data::AssetType& type) override
{
return m_createAsset();
}
void DestroyAsset(AZ::Data::AssetPtr ptr) override
{
m_destroyAsset(ptr);
}
void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override
{
assetTypes.emplace_back(m_assetType);
}
LoadResult LoadAssetData([[maybe_unused]] const AZ::Data::Asset<AZ::Data::AssetData>& asset,
[[maybe_unused]] AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
[[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB) override
{
return LoadResult::LoadComplete;
}
protected:
AZ::Data::AssetPtr(*m_createAsset)();
void(*m_destroyAsset)(AZ::Data::AssetPtr asset);
AZ::Data::AssetType m_assetType;
AZStd::unordered_set<AZ::Data::AssetId> m_ids;
};
}
@@ -0,0 +1,154 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Asset/AssetManager.h>
namespace UnitTest
{
// EmptyAsset: bare-bones asset definition, with no data contained within.
class EmptyAsset
: public AZ::Data::AssetData
{
public:
AZ_CLASS_ALLOCATOR(EmptyAsset, AZ::SystemAllocator, 0);
AZ_RTTI(EmptyAsset, "{098E3F7F-13AC-414B-9B4E-49B5AD1BD7FE}", AZ::Data::AssetData);
};
// EmptyAssetWithNoHandler: no data contained within, and no AssetHandler registered for this type
class EmptyAssetWithNoHandler
: public AZ::Data::AssetData
{
public:
AZ_RTTI(EmptyAssetWithNoHandler, "{81123022-8D45-4B5F-BBB6-3ED5DF2EFB7A}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(EmptyAssetWithNoHandler, AZ::SystemAllocator, 0);
};
// EmptyAssetWithInstanceCount: asset with no stored data, keeps track of the global number of instances currently constructed.
class EmptyAssetWithInstanceCount
: public AZ::Data::AssetData
{
public:
AZ_CLASS_ALLOCATOR(EmptyAssetWithInstanceCount, AZ::SystemAllocator, 0);
AZ_RTTI(EmptyAssetWithInstanceCount, "{C0A5DE6F-590F-4DF0-86D6-9498D4C762D8}", AZ::Data::AssetData);
EmptyAssetWithInstanceCount() { ++s_instanceCount; }
~EmptyAssetWithInstanceCount() override { --s_instanceCount; }
static void Reflect(AZ::SerializeContext& context)
{
context.Class<EmptyAssetWithInstanceCount>()
;
}
static inline int s_instanceCount = 0;
};
// AssetWithCustomData: asset with field that needs custom code for load/save (i.e. isn't serialized through ObjectStream)
class AssetWithCustomData
: public AZ::Data::AssetData
{
public:
AZ_CLASS_ALLOCATOR(AssetWithCustomData, AZ::SystemAllocator, 0);
AZ_RTTI(AssetWithCustomData, "{73D60606-BDE5-44F9-9420-5649FE7BA5B8}", AZ::Data::AssetData);
AssetWithCustomData()
: m_data(nullptr) {}
explicit AssetWithCustomData(const AZ::Data::AssetId& assetId,
const AZ::Data::AssetData::AssetStatus assetStatus = AZ::Data::AssetData::AssetStatus::NotLoaded)
: AssetData(assetId, assetStatus)
, m_data(nullptr) {}
~AssetWithCustomData() override
{
if (m_data)
{
azfree(m_data);
}
}
static void Reflect(AZ::SerializeContext& context)
{
context.Class<AssetWithCustomData>()
->Field("data", &AssetWithCustomData::m_data)
;
}
char* m_data;
};
// AssetWithSerializedData: asset with single field that is serialized in/out
class AssetWithSerializedData
: public AZ::Data::AssetData
{
public:
AZ_RTTI(AssetWithSerializedData, "{BC15ABCC-0150-44C4-976B-79A91F8A8608}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(AssetWithSerializedData, AZ::SystemAllocator, 0);
AssetWithSerializedData() = default;
static void Reflect(AZ::SerializeContext& context)
{
context.Class<AssetWithSerializedData>()
->Field("data", &AssetWithSerializedData::m_data)
;
}
float m_data = 1.f;
private:
AssetWithSerializedData(const AssetWithSerializedData&) = delete;
};
// AssetWithAssetReference: asset with single field containing a serialized asset reference
class AssetWithAssetReference
: public AZ::Data::AssetData
{
public:
AZ_RTTI(AssetWithAssetReference, "{97383A2D-B84B-46D6-B3FA-FB8E49A4407F}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(AssetWithAssetReference, AZ::SystemAllocator, 0);
AssetWithAssetReference() = default;
AssetWithAssetReference(const AssetWithAssetReference&) = delete;
static void Reflect(AZ::SerializeContext& context)
{
context.Class<AssetWithAssetReference>()
->Field("asset", &AssetWithAssetReference::m_asset);
}
AZ::Data::Asset<AZ::Data::AssetData> m_asset;
};
// AssetWithQueueAndPreLoadReferences: asset with two asset references, one set as PreLoad, and one set as QueueLoad
class AssetWithQueueAndPreLoadReferences
: public AZ::Data::AssetData
{
public:
AZ_RTTI(AssetWithQueueAndPreLoadReferences, "{B86F9FA2-7953-43F9-BBD2-31070452C841}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(AssetWithQueueAndPreLoadReferences, AZ::SystemAllocator, 0);
AssetWithQueueAndPreLoadReferences() :
m_preLoad(AZ::Data::AssetLoadBehavior::PreLoad),
m_queueLoad(AZ::Data::AssetLoadBehavior::QueueLoad)
{
}
static void Reflect(AZ::SerializeContext& context)
{
context.Class<AssetWithQueueAndPreLoadReferences>()
->Field("preLoad", &AssetWithQueueAndPreLoadReferences::m_preLoad)
->Field("queueLoad", &AssetWithQueueAndPreLoadReferences::m_queueLoad)
;
}
AZ::Data::Asset<AssetWithAssetReference> m_preLoad;
AZ::Data::Asset<AssetWithAssetReference> m_queueLoad;
private:
AssetWithQueueAndPreLoadReferences(const AssetWithQueueAndPreLoadReferences&) = delete;
};
}