Files
o3de/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp
T
Mike Balfour 6c17c7bfb3 Add new API to convert absolute source paths to relative paths. (#930)
There are already APIs for getting a relative product path from an absolute source path, or getting a relative source path for an *existing* source file, but there were no APIs for getting a relative source path for a *new* source file.  Prefabs will need this ability to be able to correctly generate a relative source path inside the prefab file before the file has been saved.
The logic for relative source paths is a little bit tricky because the paths are relative to the watch folders, and the watch folders can be nested, with different priorities to explain which should take precedence.  The input paths can also include specifiers like "." and "..", which need to be reconciled before creating the final correct relative path.  The included unit tests test all of the tricky edge cases that I was able to identify.
2021-05-26 15:30:21 -05:00

375 lines
18 KiB
C++

/*
* 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 <AzTest/AzTest.h>
#include <utilities/BatchApplicationManager.h>
#include <utilities/ApplicationServer.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <connection/connectionManager.h>
#include <QCoreApplication>
#include <QTemporaryDir>
#include <AzFramework/Network/AssetProcessorConnection.h>
namespace AssetProcessorMessagesTests
{
using namespace testing;
using ::testing::NiceMock;
using namespace AssetProcessor;
using namespace AssetBuilderSDK;
static constexpr unsigned short AssetProcessorPort = static_cast<unsigned short>(888u);
class AssetProcessorMessages;
struct UnitTestBatchApplicationManager
: BatchApplicationManager
{
UnitTestBatchApplicationManager(int* argc, char*** argv, QObject* parent)
: BatchApplicationManager(argc, argv, parent)
{
}
friend class AssetProcessorMessages;
};
class AssetProcessorMessagesTestsMockDatabaseLocationListener : public AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Handler
{
public:
MOCK_METHOD1(GetAssetDatabaseLocation, bool(AZStd::string&));
};
struct MockAssetCatalog : AssetProcessor::AssetCatalog
{
MockAssetCatalog(QObject* parent, AssetProcessor::PlatformConfiguration* platformConfiguration)
: AssetCatalog(parent, platformConfiguration)
{
}
AzFramework::AssetSystem::GetUnresolvedDependencyCountsResponse HandleGetUnresolvedDependencyCountsRequest(MessageData<AzFramework::AssetSystem::GetUnresolvedDependencyCountsRequest> messageData) override
{
m_called = true;
return AssetCatalog::HandleGetUnresolvedDependencyCountsRequest(messageData);
}
bool m_called = false;
};
struct MockAssetRequestHandler : AssetRequestHandler
{
bool InvokeHandler(MessageData<AzFramework::AssetSystem::BaseAssetProcessorMessage> message) override
{
m_invoked = true;
return AssetRequestHandler::InvokeHandler(message);
}
AZStd::atomic_bool m_invoked = false;
};
class AssetProcessorMessages
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
void SetUp() override
{
#if !AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
AssetUtilities::ResetGameName();
m_temporarySourceDir = QDir(m_temporaryDir.path());
m_databaseLocation = m_temporarySourceDir.absoluteFilePath("test_database.sqlite").toUtf8().constData();
ON_CALL(m_databaseLocationListener, GetAssetDatabaseLocation(_))
.WillByDefault(
DoAll( // set the 0th argument ref (string) to the database location and return true.
SetArgReferee<0>(m_databaseLocation.c_str()),
Return(true)));
m_databaseLocationListener.BusConnect();
m_dbConn.OpenDatabase();
int argC = 0;
m_batchApplicationManager = AZStd::make_unique<UnitTestBatchApplicationManager>(&argC, nullptr, nullptr);
m_batchApplicationManager->BeforeRun();
// Override Game Name to be "AutomatedTesting"
AssetUtilities::ComputeProjectName("AutomatedTesting", true);
m_batchApplicationManager->m_platformConfiguration = new PlatformConfiguration();
m_batchApplicationManager->InitAssetProcessorManager();
m_assetCatalog = AZStd::make_unique<MockAssetCatalog>(nullptr, m_batchApplicationManager->m_platformConfiguration);
m_batchApplicationManager->m_assetCatalog = m_assetCatalog.get();
m_batchApplicationManager->InitRCController();
m_batchApplicationManager->InitFileStateCache();
m_batchApplicationManager->InitFileMonitor();
m_batchApplicationManager->InitApplicationServer();
m_batchApplicationManager->InitConnectionManager();
// Note this must be constructed after InitConnectionManager is called since it will interact with the connection manager
m_assetRequestHandler = new MockAssetRequestHandler();
m_batchApplicationManager->InitAssetRequestHandler(m_assetRequestHandler);
m_batchApplicationManager->m_fileWatcher.StartWatching();
QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ConnectionError, [](unsigned /*connId*/, QString error)
{
AZ_Error("ConnectionManager", false, "%s", error.toUtf8().constData());
});
ASSERT_TRUE(m_batchApplicationManager->m_applicationServer->startListening(AssetProcessorPort));
using namespace AzFramework;
m_assetSystemComponent = AZStd::make_unique<AssetSystem::AssetSystemComponent>();
m_assetSystemComponent->Init();
m_assetSystemComponent->Activate();
QCoreApplication::processEvents();
RunNetworkRequest([]()
{
AZStd::string appBranchToken;
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForEngineRoot, appBranchToken);
AzFramework::AssetSystem::ConnectionSettings connectionSettings;
connectionSettings.m_assetProcessorIp = "127.0.0.1";
connectionSettings.m_assetProcessorPort = AssetProcessorPort;
connectionSettings.m_branchToken = appBranchToken;
connectionSettings.m_projectName = "AutomatedTesting";
connectionSettings.m_assetPlatform = "pc";
connectionSettings.m_connectionIdentifier = "UNITTEST";
connectionSettings.m_connectTimeout = AZStd::chrono::seconds(15);
connectionSettings.m_connectionDirection = AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor;
connectionSettings.m_waitUntilAssetProcessorIsReady = false;
connectionSettings.m_launchAssetProcessorOnFailedConnection = false;
bool result = false;
AzFramework::AssetSystemRequestBus::BroadcastResult(result,
&AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings);
ASSERT_TRUE(result);
});
#endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
}
void TearDown() override
{
#if !AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
QEventLoop eventLoop;
QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit);
m_batchApplicationManager->m_connectionManager->QuitRequested();
eventLoop.exec();
m_assetSystemComponent->Deactivate();
m_batchApplicationManager->Destroy();
#endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
}
void RunNetworkRequest(AZStd::function<void()> func) const
{
AZStd::atomic_bool finished = false;
auto start = AZStd::chrono::monotonic_clock::now();
auto thread = AZStd::thread([&finished, &func]()
{
func();
finished = true;
}
);
constexpr int MaxWaitTime = 5;
while (!finished && AZStd::chrono::monotonic_clock::now() - start < AZStd::chrono::seconds(MaxWaitTime))
{
QCoreApplication::processEvents();
}
ASSERT_TRUE(finished) << "Timeout";
thread.join();
}
protected:
MockAssetRequestHandler* m_assetRequestHandler{}; // Not owned, AP will delete this pointer
QTemporaryDir m_temporaryDir;
AZStd::unique_ptr<UnitTestBatchApplicationManager> m_batchApplicationManager;
AZStd::unique_ptr<AzFramework::AssetSystem::AssetSystemComponent> m_assetSystemComponent;
NiceMock<AssetProcessorMessagesTestsMockDatabaseLocationListener> m_databaseLocationListener;
AZStd::unique_ptr<MockAssetCatalog> m_assetCatalog = nullptr;
QDir m_temporarySourceDir;
AZStd::string m_databaseLocation;
AssetDatabaseConnection m_dbConn;
};
struct MessagePair
{
AZStd::unique_ptr<AzFramework::AssetSystem::BaseAssetProcessorMessage> m_request;
AZStd::unique_ptr<AzFramework::AssetSystem::BaseAssetProcessorMessage> m_response;
};
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
TEST_F(AssetProcessorMessages, DISABLED_All)
#else
TEST_F(AssetProcessorMessages, All)
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
{
// Test that we can successfully send network messages and have them arrive for processing
// For messages that have a response, it also verifies the response comes back
// Note that several harmless warnings will be triggered due to the messages not having any data set
using namespace AzFramework::AssetSystem;
using namespace AzToolsFramework::AssetSystem;
AZStd::vector<MessagePair> testMessages;
AZStd::unordered_map<int, AZStd::string> nameMap; // This is just for debugging, so we can output the name of failed messages
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
auto addPairFunc = [&testMessages, &nameMap, serializeContext](auto* request, auto* response)
{
testMessages.emplace_back(MessagePair{
AZStd::unique_ptr<AZStd::remove_pointer_t<decltype(request)>>(request),
AZStd::unique_ptr<AZStd::remove_pointer_t<decltype(response)>>(response)
});
auto data = serializeContext->FindClassData(request->RTTI_GetType());
nameMap[request->GetMessageType()] = data->m_name;
};
auto addRequestFunc = [&testMessages, &nameMap, serializeContext](auto* request)
{
testMessages.emplace_back(MessagePair{AZStd::unique_ptr<AZStd::remove_pointer_t<decltype(request)>>(request), nullptr });
auto data = serializeContext->FindClassData(request->RTTI_GetType());
nameMap[request->GetMessageType()] = data->m_name;
};
addPairFunc(new GetFullSourcePathFromRelativeProductPathRequest(), new GetFullSourcePathFromRelativeProductPathResponse());
addPairFunc(new GetRelativeProductPathFromFullSourceOrProductPathRequest(), new GetRelativeProductPathFromFullSourceOrProductPathResponse());
addPairFunc(
new GenerateRelativeSourcePathRequest(),
new GenerateRelativeSourcePathResponse());
addPairFunc(new SourceAssetInfoRequest(), new SourceAssetInfoResponse());
addPairFunc(new SourceAssetProductsInfoRequest(), new SourceAssetProductsInfoResponse());
addPairFunc(new GetScanFoldersRequest(), new GetScanFoldersResponse());
addPairFunc(new GetAssetSafeFoldersRequest(), new GetAssetSafeFoldersResponse());
addRequestFunc(new RegisterSourceAssetRequest());
addRequestFunc(new UnregisterSourceAssetRequest());
addPairFunc(new AssetInfoRequest(), new AssetInfoResponse());
addPairFunc(new AssetDependencyInfoRequest(), new AssetDependencyInfoResponse());
addRequestFunc(new RequestEscalateAsset());
addPairFunc(new RequestAssetStatus(), new ResponseAssetStatus());
RunNetworkRequest([&testMessages, &nameMap, this]()
{
for(auto&& pair : testMessages)
{
AZStd::string messageName = nameMap[pair.m_request->GetMessageType()];
m_assetRequestHandler->m_invoked = false;
if(pair.m_response)
{
EXPECT_TRUE(SendRequest(*pair.m_request.get(), *pair.m_response.get())) << "Message " << messageName.c_str() << " failed to send";
}
else
{
EXPECT_TRUE(SendRequest(*pair.m_request.get())) << "Message " << messageName.c_str() << " failed to send";
// Since there's no response, the above line will finish immediately, so we need to wait a little bit so the message can actually be sent
// before we check if it was received
// We'll wait a maximum of 5 seconds, checking periodically if the message was received, to avoid failing due to slow running test servers
constexpr int MaxWaitTimeSeconds = 5;
auto start = AZStd::chrono::monotonic_clock::now();
while (!m_assetRequestHandler->m_invoked && AZStd::chrono::monotonic_clock::now() - start < AZStd::chrono::seconds(MaxWaitTimeSeconds))
{
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
}
}
EXPECT_TRUE(m_assetRequestHandler->m_invoked) << "Message " << messageName.c_str() << " was not received";
}
});
}
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
TEST_F(AssetProcessorMessages, DISABLED_GetUnresolvedProductReferences_Succeeds)
#else
TEST_F(AssetProcessorMessages, GetUnresolvedProductReferences_Succeeds)
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
{
using namespace AzToolsFramework::AssetDatabase;
// Setup the database with all needed info
ScanFolderDatabaseEntry scanfolder1("scanfolder1", "scanfolder1", "scanfolder1");
ASSERT_TRUE(m_dbConn.SetScanFolder(scanfolder1));
SourceDatabaseEntry source1(scanfolder1.m_scanFolderID, "source1.png", AZ::Uuid::CreateRandom(), "Fingerprint");
SourceDatabaseEntry source2(scanfolder1.m_scanFolderID, "source2.png", AZ::Uuid::CreateRandom(), "Fingerprint");
ASSERT_TRUE(m_dbConn.SetSource(source1));
ASSERT_TRUE(m_dbConn.SetSource(source2));
JobDatabaseEntry job1(source1.m_sourceID, "jobkey", 1234, "pc", AZ::Uuid::CreateRandom(), AzToolsFramework::AssetSystem::JobStatus::Completed, 1111);
JobDatabaseEntry job2(source2.m_sourceID, "jobkey", 1234, "pc", AZ::Uuid::CreateRandom(), AzToolsFramework::AssetSystem::JobStatus::Completed, 2222);
ASSERT_TRUE(m_dbConn.SetJob(job1));
ASSERT_TRUE(m_dbConn.SetJob(job2));
ProductDatabaseEntry product1(job1.m_jobID, 5, "source1.product", AZ::Data::AssetType::CreateRandom());
ProductDatabaseEntry product2(job2.m_jobID, 15, "source2.product", AZ::Data::AssetType::CreateRandom());
ASSERT_TRUE(m_dbConn.SetProduct(product1));
ASSERT_TRUE(m_dbConn.SetProduct(product2));
ProductDependencyDatabaseEntry dependency1(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, "somefileA.txt", ProductDependencyDatabaseEntry::DependencyType::ProductDep_SourceFile);
ProductDependencyDatabaseEntry dependency2(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, "somefileB.txt", ProductDependencyDatabaseEntry::DependencyType::ProductDep_ProductFile);
ProductDependencyDatabaseEntry dependency3(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, "somefileC.txt");
ProductDependencyDatabaseEntry dependency4(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, ":somefileD.txt"); // Exclusion
ProductDependencyDatabaseEntry dependency5(product1.m_productID, AZ::Uuid::CreateNull(), 0, {}, "pc", 0, "somefileE*.txt"); // Wildcard
ASSERT_TRUE(m_dbConn.SetProductDependency(dependency1));
ASSERT_TRUE(m_dbConn.SetProductDependency(dependency2));
ASSERT_TRUE(m_dbConn.SetProductDependency(dependency3));
ASSERT_TRUE(m_dbConn.SetProductDependency(dependency4));
ASSERT_TRUE(m_dbConn.SetProductDependency(dependency5));
// Setup the asset catalog
AzFramework::AssetSystem::AssetNotificationMessage assetNotificationMessage("source1.product", AzFramework::AssetSystem::AssetNotificationMessage::NotificationType::AssetChanged, AZ::Data::AssetType::CreateRandom(), "pc");
assetNotificationMessage.m_assetId = AZ::Data::AssetId(source1.m_sourceGuid, product1.m_subID);
assetNotificationMessage.m_dependencies.push_back(AZ::Data::ProductDependency(AZ::Data::AssetId(source2.m_sourceGuid, product2.m_subID), {}));
m_assetCatalog->OnAssetMessage(assetNotificationMessage);
// Run the actual test
RunNetworkRequest([&source1, &product1]()
{
using namespace AzFramework;
AZ::u32 assetReferenceCount, pathReferenceCount;
AZ::Data::AssetId assetId = AZ::Data::AssetId(source1.m_sourceGuid, product1.m_subID);
AssetSystemRequestBus::Broadcast(&AssetSystemRequestBus::Events::GetUnresolvedProductReferences, assetId, assetReferenceCount, pathReferenceCount);
ASSERT_EQ(assetReferenceCount, 1);
ASSERT_EQ(pathReferenceCount, 3);
});
ASSERT_TRUE(m_assetCatalog->m_called);
}
}