714f5357b2
* Add an error message to AP when bad project path Produce a log error or a dialog box error when the project path for AP does not have a project.json and is invalid. Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com> * Fix a failing unit test - AssetProcessorMessages Adding a check for 'project.json' caused BeforeRun() in a test fixture to fail. Teardown of the fixture was also broken if the test failed to fully startup the application manager, so added null checks there. Added an assert to the fixture's Setup to check the status of BeforeRun(). Added additional settings registry setup to the fixture to make sure the project path and branch token are configured before BeforeRun() is called. Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com>
382 lines
18 KiB
C++
382 lines
18 KiB
C++
/*
|
|
* 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 <AzTest/AzTest.h>
|
|
#include <utilities/BatchApplicationManager.h>
|
|
#include <utilities/ApplicationServer.h>
|
|
#include <AzFramework/Asset/AssetSystemComponent.h>
|
|
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
|
#include <AzCore/UnitTest/TestTypes.h>
|
|
#include <AzCore/Utils/Utils.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{65535u};
|
|
|
|
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
|
|
{
|
|
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);
|
|
|
|
auto registry = AZ::SettingsRegistry::Get();
|
|
EXPECT_NE(registry, nullptr);
|
|
constexpr AZ::SettingsRegistryInterface::FixedValueString bootstrapKey{
|
|
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey
|
|
};
|
|
constexpr AZ::SettingsRegistryInterface::FixedValueString projectPathKey{ bootstrapKey + "/project_path" };
|
|
registry->Set(projectPathKey, "AutomatedTesting");
|
|
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
|
|
|
// Force the branch token into settings registry before starting the application manager.
|
|
// This avoids writing the asset_processor.setreg file which can cause fileIO errors.
|
|
const AZ::IO::FixedMaxPathString enginePath = AZ::Utils::GetEnginePath();
|
|
constexpr AZ::SettingsRegistryInterface::FixedValueString branchTokenKey{ bootstrapKey + "/assetProcessor_branch_token" };
|
|
AZStd::string token;
|
|
AZ::StringFunc::AssetPath::CalculateBranchToken(enginePath.c_str(), token);
|
|
registry->Set(branchTokenKey, token.c_str());
|
|
|
|
auto status = m_batchApplicationManager->BeforeRun();
|
|
ASSERT_EQ(status, ApplicationManager::BeforeRunStatus::Status_Success);
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
void TearDown() override
|
|
{
|
|
if (m_batchApplicationManager->m_connectionManager)
|
|
{
|
|
QEventLoop eventLoop;
|
|
|
|
QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit);
|
|
|
|
m_batchApplicationManager->m_connectionManager->QuitRequested();
|
|
|
|
eventLoop.exec();
|
|
}
|
|
|
|
if (m_assetSystemComponent)
|
|
{
|
|
m_assetSystemComponent->Deactivate();
|
|
}
|
|
m_batchApplicationManager->Destroy();
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
TEST_F(AssetProcessorMessages, All)
|
|
{
|
|
// 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";
|
|
}
|
|
});
|
|
}
|
|
|
|
TEST_F(AssetProcessorMessages, GetUnresolvedProductReferences_Succeeds)
|
|
{
|
|
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);
|
|
}
|
|
}
|