123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- /*
- * 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/Asset/AssetDataStream.h>
- #include <AzCore/IO/Streamer/FileRequest.h>
- #include <AzCore/UnitTest/TestTypes.h>
- #include <AZTestShared/Utils/Utils.h>
- #include <Tests/Streamer/IStreamerMock.h>
- class AssetDataStreamTest
- : public UnitTest::LeakDetectionFixture
- {
- public:
- void SetUp() override
- {
- using ::testing::_;
- using ::testing::NiceMock;
- using ::testing::Return;
- UnitTest::LeakDetectionFixture::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::LeakDetectionFixture::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](
- [[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();
- }
|