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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,359 @@
/*
* 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
{
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 "SamplesProject"
AssetUtilities::ComputeGameName("SamplesProject", 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::CalculateBranchTokenForAppRoot, appBranchToken);
AzFramework::AssetSystem::ConnectionSettings connectionSettings;
connectionSettings.m_assetProcessorIp = "127.0.0.1";
connectionSettings.m_assetProcessorPort = AssetProcessorPort;
connectionSettings.m_branchToken = appBranchToken;
connectionSettings.m_projectName = "SamplesProject";
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
{
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();
}
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 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);
}
}
@@ -0,0 +1,167 @@
/*
* 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 "AssetProcessorTest.h"
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include "BaseAssetProcessorTest.h"
#include <native/utilities/BatchApplicationManager.h>
#include <native/connection/connectionManager.h>
#include <QCoreApplication>
AZ_UNIT_TEST_HOOK(new BaseAssetProcessorTestEnvironment)
namespace AssetProcessor
{
class UnitTestAppManager : public BatchApplicationManager
{
public:
explicit UnitTestAppManager(int* argc, char*** argv)
: BatchApplicationManager(argc, argv)
{}
bool PrepareForTests()
{
if (!ApplicationManager::Activate())
{
return false;
}
// tests which use the builder bus plug in their own mock version, so disconnect ours.
AssetProcessor::AssetBuilderInfoBus::Handler::BusDisconnect();
// Disable saving global user settings to prevent failure due to detecting file updates
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
m_platformConfig.reset(new AssetProcessor::PlatformConfiguration);
m_connectionManager.reset(new ConnectionManager(m_platformConfig.get()));
RegisterObjectForQuit(m_connectionManager.get());
return true;
}
AZStd::unique_ptr<AssetProcessor::PlatformConfiguration> m_platformConfig;
AZStd::unique_ptr<ConnectionManager> m_connectionManager;
};
class LegacyTestAdapter : public AssetProcessorTest,
public ::testing::WithParamInterface<std::string>
{
void SetUp() override
{
AssetProcessorTest::SetUp();
static int numParams = 1;
static char processName[] = {"AssetProcessorBatch"};
static char* namePtr = &processName[0];
static char** paramStringArray = &namePtr;
m_application.reset(new UnitTestAppManager(&numParams, &paramStringArray));
ASSERT_EQ(m_application->BeforeRun(), ApplicationManager::Status_Success);
ASSERT_TRUE(m_application->PrepareForTests());
}
void TearDown() override
{
m_application.reset();
AssetProcessorTest::TearDown();
}
AZStd::unique_ptr<UnitTestAppManager> m_application;
};
// use the list of registered legacy unit tests to generate the list of test parameters:
std::vector<std::string> GenerateTestCases()
{
std::vector<std::string> names;
UnitTestRegistry* currentTest = UnitTestRegistry::first();
while (currentTest)
{
names.push_back(currentTest->getName());
currentTest = currentTest->next();
}
return names;
}
// use the above generator function to decide what the name of the test is
// instead of just showing "0" "1" etc
std::string GenerateTestName(const ::testing::TestParamInfo<std::string>& info)
{
return info.param;
}
TEST_P(LegacyTestAdapter, AllTests)
{
// this is a generator test function. This will be called repeatedly based on the above
// generator function. Each time, it will set GetParam() to be the generated value.
// doing just one at a time per setup and teardown makes sure each one works on its own and doesn't
// interfere with the others.
UnitTestRegistry* currentTest = UnitTestRegistry::first();
while (currentTest)
{
if (azstricmp(currentTest->getName(), GetParam().c_str()) == 0)
{
UnitTestRun* actualTest = currentTest->create();
volatile bool testIsComplete = false;
QString failMessage;
QObject::connect(actualTest, &UnitTestRun::UnitTestPassed, [&testIsComplete]()
{
testIsComplete = true;
});
QObject::connect(actualTest, &UnitTestRun::UnitTestFailed, [&testIsComplete, &failMessage](QString message)
{
testIsComplete = true;
failMessage = message;
});
QElapsedTimer time;
time.start();
actualTest->StartTest();
while (!testIsComplete)
{
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
QCoreApplication::processEvents();
// operation
if (time.elapsed() > 120 * 1000) // (ms) no test, even in debug, takes longer than two minutes
{
testIsComplete = true;
failMessage = QString("Legacy test deadlocked or timed out.");
}
}
// Explanation of below: EXPECT_TRUE returns an object that can be used with the stream operator
// to add additional information when it fails, for display to the user.
EXPECT_TRUE(failMessage.isEmpty()) << failMessage.toUtf8().constData();
delete actualTest;
}
currentTest = currentTest->next();
}
}
INSTANTIATE_TEST_CASE_P(
Test,
LegacyTestAdapter,
testing::ValuesIn(GenerateTestCases()),
GenerateTestName);
};
@@ -0,0 +1,74 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzFramework/Application/Application.h>
#include <native/utilities/assetUtils.h>
#include <native/unittests/UnitTestRunner.h> // for the assert absorber.
#include <AssetManager/FileStateCache.h>
namespace AssetProcessor
{
// This is an utility class for Asset Processor Tests
// Any gmock based fixture class can derived from this class and this will automatically do system allocation and teardown for you
// It is important to note that if you are overriding Setup and Teardown functions of your fixture class than please call the base class functions.
class AssetProcessorTest
: public ::testing::Test
{
protected:
UnitTestUtils::AssertAbsorber* m_errorAbsorber;
FileStatePassthrough m_fileStateCache;
void SetUp() override
{
if (!AZ::AllocatorInstance<AZ::OSAllocator>::IsReady())
{
m_ownsOSAllocator = true;
AZ::AllocatorInstance<AZ::OSAllocator>::Create();
}
if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
{
m_ownsSysAllocator = true;
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
}
m_errorAbsorber = new UnitTestUtils::AssertAbsorber();
m_application = AZStd::make_unique<AzFramework::Application>();
}
void TearDown() override
{
AssetUtilities::ResetAssetRoot();
m_application.reset();
delete m_errorAbsorber;
m_errorAbsorber = nullptr;
if (m_ownsSysAllocator)
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
m_ownsSysAllocator = false;
}
if (m_ownsOSAllocator)
{
AZ::AllocatorInstance<AZ::OSAllocator>::Destroy();
m_ownsOSAllocator = false;
}
}
bool m_ownsOSAllocator = false;
bool m_ownsSysAllocator = false;
AZStd::unique_ptr<AzFramework::Application> m_application;
};
}
@@ -0,0 +1,59 @@
/*
* 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 <qlogging.h>
#include <QString>
// Environments subclass from AZ::Test::ITestEnvironment
class BaseAssetProcessorTestEnvironment : public AZ::Test::ITestEnvironment
{
public:
virtual ~BaseAssetProcessorTestEnvironment() {}
protected:
// Any time Qt emits a warning, critical, or fatal, consider the test to have failed!
static void UnitTestMessageHandler(QtMsgType type, const QMessageLogContext& /*context*/, const QString& msg)
{
switch (type)
{
case QtDebugMsg:
break;
case QtWarningMsg:
EXPECT_FALSE("QtWarningMsg") << msg.toUtf8().constData();
break;
case QtCriticalMsg:
EXPECT_FALSE("QtCriticalMsg") << msg.toUtf8().constData();
break;
case QtFatalMsg:
EXPECT_FALSE("QtFatalMsg") << msg.toUtf8().constData();
break;
}
}
// There are two pure-virtual functions to implement, setup and teardown
void SetupEnvironment() override
{
// Setup code
qInstallMessageHandler(UnitTestMessageHandler);
}
void TeardownEnvironment() override
{
qInstallMessageHandler(nullptr);
}
private:
// Put members that need to be maintained throughout testing lifecycle here
// Don't declare them in the setup/teardown functions!
};
@@ -0,0 +1,148 @@
/*
* 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 "native/utilities/BuilderConfigurationManager.h"
#include <native/unittests/UnitTestRunner.h>
#include <AzCore/UnitTest/TestTypes.h>
class BuilderConfigurationTests
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
BuilderConfigurationTests()
{
}
virtual ~BuilderConfigurationTests()
{
}
void SetUp() override
{
}
void TearDown() override
{
}
void CreateTestConfig(QString iniStr, AssetProcessor::BuilderConfigurationManager& configurationManager)
{
QDir tempPath(m_tempDir.path());
UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath(AssetProcessor::BuilderConfigFile).toUtf8().data(), iniStr);
configurationManager.LoadConfiguration(tempPath.absoluteFilePath(AssetProcessor::BuilderConfigFile).toUtf8().data());
}
QTemporaryDir m_tempDir;
};
const char SampleConfig[] =
"[Job PNG Compile]\n"
"checkServer=true\n"
"priority=3\n"
"critical=true\n"
"checkExclusiveLock=true\n"
"fingerprint=finger\n"
"jobFingerprint=somejob7\n"
"params=something=true,otherthing,somethingelse=7\n"
"[Builder Image Worker Builder]\n"
"fingerprint=fingerprint11\n"
"version=7\n"
"patterns=*.png\n"
"[Job TIFF Compile]\n"
"checkServer=false\n"
"priority=9\n"
"critical=false\n"
"checkExclusiveLock=true\n"
"fingerprint=fingerprint1\n"
"params=something=false,otheing,somethingelse=6\n";
TEST_F(BuilderConfigurationTests, TestBuilderConfig_LoadConfig_Success)
{
AssetProcessor::BuilderConfigurationManager builderConfig;
CreateTestConfig(SampleConfig, builderConfig);
ASSERT_TRUE(builderConfig.IsLoaded());
}
TEST_F(BuilderConfigurationTests, TestBuilderConfig_InvalidKey_NoUpdate)
{
AssetProcessor::BuilderConfigurationManager builderConfig;
CreateTestConfig(SampleConfig, builderConfig);
AssetBuilderSDK::JobDescriptor baseDescriptor;
AssetBuilderSDK::JobDescriptor testDescriptor;
// Verify an undefined key does not update our data
ASSERT_FALSE(builderConfig.UpdateJobDescriptor("False Key", testDescriptor));
ASSERT_EQ(testDescriptor.m_checkServer, baseDescriptor.m_checkServer);
ASSERT_EQ(testDescriptor.m_critical, baseDescriptor.m_critical);
ASSERT_EQ(testDescriptor.m_priority, baseDescriptor.m_priority);
ASSERT_EQ(testDescriptor.m_checkExclusiveLock, baseDescriptor.m_checkExclusiveLock);
ASSERT_EQ(testDescriptor.m_additionalFingerprintInfo, baseDescriptor.m_additionalFingerprintInfo);
ASSERT_EQ(testDescriptor.m_jobParameters, baseDescriptor.m_jobParameters);
}
TEST_F(BuilderConfigurationTests, TestBuilderConfig_JobEntry_Success)
{
AssetProcessor::BuilderConfigurationManager builderConfig;
CreateTestConfig(SampleConfig, builderConfig);
AssetBuilderSDK::JobDescriptor testDescriptor;
// Verify a JobEntry makes the expected updates from data
ASSERT_TRUE(builderConfig.UpdateJobDescriptor("PNG Compile", testDescriptor));
ASSERT_EQ(testDescriptor.m_checkServer, true);
ASSERT_EQ(testDescriptor.m_critical, true);
ASSERT_EQ(testDescriptor.m_priority, 3);
ASSERT_EQ(testDescriptor.m_checkExclusiveLock, true);
ASSERT_EQ(testDescriptor.m_additionalFingerprintInfo, "finger");
ASSERT_EQ(testDescriptor.m_jobParameters[AZ_CRC("something", 0x09da31fb)], "true");
ASSERT_EQ(testDescriptor.m_jobParameters[AZ_CRC("somethingelse", 0x237edebb)], "7");
ASSERT_NE(testDescriptor.m_jobParameters.find(AZ_CRC("otherthing", 0x6f2d0a4a)), testDescriptor.m_jobParameters.end());
}
TEST_F(BuilderConfigurationTests, TestBuilderConfig_SecondJobEntry_Success)
{
AssetProcessor::BuilderConfigurationManager builderConfig;
CreateTestConfig(SampleConfig, builderConfig);
AssetBuilderSDK::JobDescriptor testDescriptor;
// Verify a second JobEntry defined in an .ini file makes the expected updates from data
ASSERT_TRUE(builderConfig.UpdateJobDescriptor("TIFF Compile", testDescriptor));
ASSERT_EQ(testDescriptor.m_checkServer, false);
ASSERT_EQ(testDescriptor.m_critical, false);
ASSERT_EQ(testDescriptor.m_priority, 9);
ASSERT_EQ(testDescriptor.m_checkExclusiveLock, true);
ASSERT_EQ(testDescriptor.m_additionalFingerprintInfo, "fingerprint1");
ASSERT_EQ(testDescriptor.m_jobParameters[AZ_CRC("something", 0x09da31fb)], "false");
ASSERT_EQ(testDescriptor.m_jobParameters[AZ_CRC("somethingelse", 0x237edebb)], "6");
ASSERT_NE(testDescriptor.m_jobParameters.find(AZ_CRC("otheing", 0xba35d565)), testDescriptor.m_jobParameters.end());
}
TEST_F(BuilderConfigurationTests, TestBuilderConfig_BuilderEntry_Success)
{
AssetProcessor::BuilderConfigurationManager builderConfig;
CreateTestConfig(SampleConfig, builderConfig);
// Verify a Builder makes the expected updates from data
AssetBuilderSDK::AssetBuilderDesc testBuilder;
ASSERT_TRUE(builderConfig.UpdateBuilderDescriptor("Image Worker Builder", testBuilder));
ASSERT_EQ(testBuilder.m_analysisFingerprint, "fingerprint11");
ASSERT_EQ(testBuilder.m_version, 7);
ASSERT_EQ(testBuilder.m_patterns.size(), 1);
ASSERT_EQ(testBuilder.m_patterns[0].m_pattern, "*.png");
}
@@ -0,0 +1,181 @@
/*
* 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 "FileProcessorTests.h"
namespace UnitTests
{
constexpr int ConnectionBusId = 0;
void FileProcessorTests::SetUp()
{
AssetProcessorTest::SetUp();
ConnectionBus::Handler::BusConnect(ConnectionBusId);
m_data.reset(new StaticData());
m_data->m_databaseLocationListener.BusConnect();
m_data->m_temporarySourceDir = QDir(m_data->m_temporaryDir.path());
// in other unit tests we may open the database called ":memory:" to use an in-memory database instead of one on disk.
// in this test, however, we use a real database, because the file processor shares it and opens its own connection to it.
// ":memory:" databases are one-instance-only, and even if another connection is opened to ":memory:" it would
// not share with others created using ":memory:" and get a unique database instead.
m_data->m_databaseLocation = m_data->m_temporarySourceDir.absoluteFilePath("test_database.sqlite").toUtf8().constData();
ON_CALL(m_data->m_databaseLocationListener, GetAssetDatabaseLocation(_))
.WillByDefault(
DoAll( // set the 0th argument ref (string) to the database location and return true.
SetArgReferee<0>(m_data->m_databaseLocation),
Return(true)));
// Initialize the database:
m_data->m_connection.ClearData(); // this is expected to reset/clear/reopen
m_data->m_config = AZStd::make_unique<AssetProcessor::PlatformConfiguration>();
m_data->m_config->EnablePlatform({ "pc", { "host", "renderer", "desktop" } }, true);
m_data->m_fileProcessor = AZStd::make_unique<FileProcessor>(m_data->m_config.get());
m_data->m_scanFolder = { m_data->m_temporarySourceDir.absolutePath().toUtf8().constData(), "dev", "rootportkey", "" };
ASSERT_TRUE(m_data->m_connection.SetScanFolder(m_data->m_scanFolder));
m_data->m_config->AddScanFolder(ScanFolderInfo(m_data->m_temporarySourceDir.absolutePath(), "dev", "rootportkey", "", false, true, m_data->m_config->GetEnabledPlatforms(), 0, m_data->m_scanFolder.m_scanFolderID));
for (int index = 0; index < 10; ++index)
{
FileDatabaseEntry entry;
entry.m_fileName = AZStd::string::format("somefile_%d.tif", index);
entry.m_isFolder = false;
entry.m_modTime = 0;
entry.m_scanFolderPK = m_data->m_scanFolder.m_scanFolderID;
m_data->m_fileEntries.push_back(entry);
}
}
void FileProcessorTests::TearDown()
{
m_data->m_databaseLocationListener.BusDisconnect();
m_data.reset();
ConnectionBus::Handler::BusDisconnect(ConnectionBusId);
AssetProcessorTest::TearDown();
}
size_t FileProcessorTests::Send([[maybe_unused]] unsigned int serial, [[maybe_unused]] const AzFramework::AssetSystem::BaseAssetProcessorMessage& message)
{
m_data->m_messagesSent++;
return 0;
}
TEST_F(FileProcessorTests, FilesAdded_WhenSentMultipleAdds_ShouldEmitOnlyOneAdd)
{
QSet<AssetFileInfo> scannerFiles;
m_data->m_fileProcessor->AssessAddedFile(m_data->m_temporarySourceDir.absoluteFilePath(m_data->m_fileEntries[0].m_fileName.c_str()));
m_data->m_fileProcessor->AssessAddedFile(m_data->m_temporarySourceDir.absoluteFilePath(m_data->m_fileEntries[0].m_fileName.c_str()));
ASSERT_EQ(m_data->m_messagesSent, 1);
}
TEST_F(FileProcessorTests, FilesFromScanner_ShouldSaveToDatabaseWithoutCreatingDuplicates)
{
QSet<AssetFileInfo> scannerFiles;
auto* scanFolder = m_data->m_config->GetScanFolderByPath(m_data->m_scanFolder.m_scanFolder.c_str());
ASSERT_NE(scanFolder, nullptr);
for (const auto& file : m_data->m_fileEntries)
{
scannerFiles.insert(AssetFileInfo(m_data->m_temporarySourceDir.absoluteFilePath(file.m_fileName.c_str()), QDateTime::fromMSecsSinceEpoch(file.m_modTime), 1234, scanFolder, file.m_isFolder));
}
m_data->m_fileProcessor->AssessFilesFromScanner(scannerFiles);
m_data->m_fileProcessor->Sync();
// Run again to make sure we don't get duplicate entries
m_data->m_fileProcessor->AssessFilesFromScanner(scannerFiles);
m_data->m_fileProcessor->Sync();
FileDatabaseEntryContainer actualEntries;
auto filesFunction = [&actualEntries](AzToolsFramework::AssetDatabase::FileDatabaseEntry& entry)
{
actualEntries.push_back(entry);
return true;
};
ASSERT_TRUE(m_data->m_connection.QueryFilesTable(filesFunction));
ASSERT_THAT(m_data->m_fileEntries, testing::UnorderedElementsAreArray(actualEntries));
}
TEST_F(FileProcessorTests, FilesFromScanner_ShouldHandleChangesBetweenSyncs)
{
QSet<AssetFileInfo> scannerFiles;
auto* scanFolder = m_data->m_config->GetScanFolderByPath(m_data->m_scanFolder.m_scanFolder.c_str());
ASSERT_NE(scanFolder, nullptr);
for (const auto& file : m_data->m_fileEntries)
{
scannerFiles.insert(AssetFileInfo(m_data->m_temporarySourceDir.absoluteFilePath(file.m_fileName.c_str()), QDateTime::fromMSecsSinceEpoch(file.m_modTime), 1234, scanFolder, file.m_isFolder));
}
m_data->m_fileProcessor->AssessFilesFromScanner(scannerFiles);
m_data->m_fileProcessor->Sync();
FileDatabaseEntryContainer actualEntries;
auto filesFunction = [&actualEntries](AzToolsFramework::AssetDatabase::FileDatabaseEntry& entry)
{
actualEntries.push_back(entry);
return true;
};
ASSERT_TRUE(m_data->m_connection.QueryFilesTable(filesFunction));
ASSERT_THAT(m_data->m_fileEntries, testing::UnorderedElementsAreArray(actualEntries));
// Clear the db (we don't have the file IDs in m_fileEntries to remove 1 by 1 so its easier to just remove them all)
for (const auto& file : actualEntries)
{
m_data->m_connection.RemoveFile(file.m_fileID);
}
// Remove two files
m_data->m_fileEntries.erase(m_data->m_fileEntries.begin());
m_data->m_fileEntries.erase(m_data->m_fileEntries.begin());
// Add a file
FileDatabaseEntry entry;
entry.m_fileName = AZStd::string::format("somefile_%d.tif", 11);
entry.m_isFolder = false;
entry.m_modTime = 0;
entry.m_scanFolderPK = m_data->m_scanFolder.m_scanFolderID;
m_data->m_fileEntries.push_back(entry);
scannerFiles.clear();
for (const auto& file : m_data->m_fileEntries)
{
scannerFiles.insert(AssetFileInfo(m_data->m_temporarySourceDir.absoluteFilePath(file.m_fileName.c_str()), QDateTime::fromMSecsSinceEpoch(file.m_modTime), 1234, scanFolder, file.m_isFolder));
}
// Sync again
m_data->m_fileProcessor->AssessFilesFromScanner(scannerFiles);
m_data->m_fileProcessor->Sync();
actualEntries.clear();
ASSERT_TRUE(m_data->m_connection.QueryFilesTable(filesFunction));
ASSERT_THAT(m_data->m_fileEntries, testing::UnorderedElementsAreArray(actualEntries));
}
}
@@ -0,0 +1,114 @@
/*
* 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 <AzTest/AzTest.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include "native/tests/AssetProcessorTest.h"
#include "AzToolsFramework/API/AssetDatabaseBus.h"
#include "AssetDatabase/AssetDatabase.h"
#include "FileProcessor/FileProcessor.h"
#include "utilities/PlatformConfiguration.h"
#include <QCoreApplication>
#include <utilities/AssetUtilEBusHelper.h>
namespace UnitTests
{
using namespace testing;
using ::testing::NiceMock;
using namespace AssetProcessor;
using AzToolsFramework::AssetDatabase::ProductDatabaseEntry;
using AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry;
using AzToolsFramework::AssetDatabase::SourceDatabaseEntry;
using AzToolsFramework::AssetDatabase::SourceFileDependencyEntry;
using AzToolsFramework::AssetDatabase::SourceFileDependencyEntryContainer;
using AzToolsFramework::AssetDatabase::JobDatabaseEntry;
using AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer;
using AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry;
using AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer;
using AzToolsFramework::AssetDatabase::AssetDatabaseConnection;
using AzToolsFramework::AssetDatabase::FileDatabaseEntry;
using AzToolsFramework::AssetDatabase::FileDatabaseEntryContainer;
class FileProcessorTestsMockDatabaseLocationListener : public AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Handler
{
public:
MOCK_METHOD1(GetAssetDatabaseLocation, bool(AZStd::string&));
};
class FileProcessorTests
: public AssetProcessorTest,
public ConnectionBus::Handler
{
public:
void SetUp() override;
void TearDown() override;
//////////////////////////////////////////////////////////////////////////
// Sends an unsolicited message to the connection
size_t Send(unsigned int serial, const AzFramework::AssetSystem::BaseAssetProcessorMessage& message) override;
// Sends a raw buffer to the connection
size_t SendRaw([[maybe_unused]] unsigned int type, [[maybe_unused]] unsigned int serial, [[maybe_unused]] const QByteArray& data) override { return 0; };
// Sends a message to the connection if the platform match
size_t SendPerPlatform([[maybe_unused]] unsigned int serial, [[maybe_unused]] const AzFramework::AssetSystem::BaseAssetProcessorMessage& message, [[maybe_unused]] const QString& platform) override { return 0; };
// Sends a raw buffer to the connection if the platform match
size_t SendRawPerPlatform([[maybe_unused]] unsigned int type, [[maybe_unused]] unsigned int serial, [[maybe_unused]] const QByteArray& data, [[maybe_unused]] const QString& platform) override { return 0; };
// Sends a message to the connection which expects a response.
unsigned int SendRequest([[maybe_unused]] const AzFramework::AssetSystem::BaseAssetProcessorMessage& message, [[maybe_unused]] const ResponseCallback& callback) override { return 0; };
// Sends a response to the connection
size_t SendResponse([[maybe_unused]] unsigned int serial, [[maybe_unused]] const AzFramework::AssetSystem::BaseAssetProcessorMessage& message) override { return 0; };
// Removes a response handler that is no longer needed
void RemoveResponseHandler([[maybe_unused]] unsigned int serial) override {};
protected:
struct StaticData
{
QTemporaryDir m_temporaryDir;
QDir m_temporarySourceDir;
// these variables are created during SetUp() and destroyed during TearDown() and thus are always available during tests using this fixture:
AZStd::string m_databaseLocation;
NiceMock<FileProcessorTestsMockDatabaseLocationListener> m_databaseLocationListener;
AssetProcessor::AssetDatabaseConnection m_connection;
AZStd::unique_ptr<AssetProcessor::PlatformConfiguration> m_config;
// The following database entry variables are initialized only when you call coverage test data CreateCoverageTestData().
// Tests which don't need or want a pre-made database should not call CreateCoverageTestData() but note that in that case
// these entries will be empty and their identifiers will be -1.
ScanFolderDatabaseEntry m_scanFolder;
AZStd::unique_ptr<FileProcessor> m_fileProcessor;
FileDatabaseEntryContainer m_fileEntries;
QCoreApplication m_coreApp;
int m_argc = 0;
int m_messagesSent = 0;
StaticData() : m_coreApp(m_argc, nullptr)
{
}
};
// we store the above data in a unique_ptr so that its memory can be cleared during TearDown() in one call, before we destroy the memory
// allocator, reducing the chance of missing or forgetting to destroy one in the future.
AZStd::unique_ptr<StaticData> m_data;
};
}
@@ -0,0 +1,171 @@
/*
* 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 "FileStateCacheTests.h"
#include <native/utilities/assetUtils.h>
#include <native/unittests/UnitTestRunner.h>
namespace UnitTests
{
void FileStateCacheTests::SetUp()
{
m_temporarySourceDir = QDir(m_temporaryDir.path());
m_fileStateCache = AZStd::make_unique<FileStateCache>();
}
void FileStateCacheTests::TearDown()
{
m_fileStateCache = nullptr;
}
void FileStateCacheTests::CheckForFile(QString path, bool shouldExist)
{
bool exists = false;
FileStateInfo fileInfo;
auto* fileStateInterface = AZ::Interface<IFileStateRequests>::Get();
ASSERT_NE(fileStateInterface, nullptr);
exists = fileStateInterface->Exists(path);
ASSERT_EQ(exists, shouldExist);
exists = fileStateInterface->GetFileInfo(path, &fileInfo);
ASSERT_EQ(exists, shouldExist);
if (exists)
{
ASSERT_EQ(AssetUtilities::NormalizeFilePath(fileInfo.m_absolutePath), AssetUtilities::NormalizeFilePath(path));
ASSERT_FALSE(fileInfo.m_isDirectory);
ASSERT_EQ(fileInfo.m_fileSize, 0);
}
}
TEST_F(FileStateCacheTests, QueryFile_ShouldNotExist)
{
QString testPath = m_temporarySourceDir.absoluteFilePath("test.txt");
// Make the file but don't tell the cache about it
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(testPath));
CheckForFile(testPath, false);
}
TEST_F(FileStateCacheTests, QueryAddedFile_ShouldExist)
{
QString testPath = m_temporarySourceDir.absoluteFilePath("test.txt");
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(testPath));
m_fileStateCache->AddFile(testPath);
CheckForFile(testPath, true);
}
TEST_F(FileStateCacheTests, QueryBulkAddedFile_ShouldExist)
{
QString testPath = m_temporarySourceDir.absoluteFilePath("test.txt");
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(testPath));
QSet<AssetFileInfo> infoSet;
AssetFileInfo fileInfo;
fileInfo.m_filePath = testPath;
fileInfo.m_isDirectory = false;
fileInfo.m_fileSize = 0;
fileInfo.m_modTime = QFileInfo(testPath).lastModified();
infoSet.insert(fileInfo);
m_fileStateCache->AddInfoSet(infoSet);
CheckForFile(testPath, true);
}
TEST_F(FileStateCacheTests, QueryRemovedFile_ShouldNotExist)
{
QString testPath = m_temporarySourceDir.absoluteFilePath("test.txt");
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(testPath));
m_fileStateCache->AddFile(testPath);
m_fileStateCache->RemoveFile(testPath);
CheckForFile(testPath, false);
}
TEST_F(FileStateCacheTests, AddAndRemoveFolder_ShouldAddAndRemoveSubFiles)
{
QDir testFolder = m_temporarySourceDir.absoluteFilePath("subfolder");
QString testPath1 = testFolder.absoluteFilePath("test1.txt");
QString testPath2 = testFolder.absoluteFilePath("test2.txt");
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(testPath1));
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(testPath2));
m_fileStateCache->AddFile(testFolder.absolutePath());
CheckForFile(testPath1, true);
CheckForFile(testPath2, true);
m_fileStateCache->RemoveFile(testFolder.absolutePath());
CheckForFile(testPath1, false);
CheckForFile(testPath2, false);
}
TEST_F(FileStateCacheTests, UpdateFileAndQuery_ShouldExist)
{
QString testPath = m_temporarySourceDir.absoluteFilePath("test.txt");
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(testPath));
QSet<AssetFileInfo> infoSet;
AssetFileInfo fileInfo;
fileInfo.m_filePath = testPath;
fileInfo.m_isDirectory = false;
fileInfo.m_fileSize = 1234; // Setting the file size to non-zero (even though the actual file is 0), UpdateFile should update this to 0 and allow CheckForFile to pass as a result
fileInfo.m_modTime = QFileInfo(testPath).lastModified();
infoSet.insert(fileInfo);
m_fileStateCache->AddInfoSet(infoSet);
m_fileStateCache->UpdateFile(testPath);
CheckForFile(testPath, true);
}
TEST_F(FileStateCacheTests, PassthroughTest)
{
m_fileStateCache = nullptr; // Need to release the existing one first since only one handler can exist for the ebus
m_fileStateCache = AZStd::make_unique<FileStatePassthrough>();
QString testPath = m_temporarySourceDir.absoluteFilePath("test.txt");
CheckForFile(testPath, false);
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(testPath));
CheckForFile(testPath, true);
}
TEST_F(FileStateCacheTests, HandlesMixedSeperators)
{
QSet<AssetFileInfo> infoSet;
AssetFileInfo fileInfo;
fileInfo.m_filePath = R"(c:\some/test\file.txt)";
infoSet.insert(fileInfo);
m_fileStateCache->AddInfoSet(infoSet);
CheckForFile(R"(c:\some\test\file.txt)", true);
CheckForFile(R"(c:/some/test/file.txt)", true);
}
}
@@ -0,0 +1,38 @@
/*
* 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 <AzTest/AzTest.h>
#include <AssetManager/FileStateCache.h>
#include <QTemporaryDir>
namespace UnitTests
{
using namespace testing;
using ::testing::NiceMock;
using namespace AssetProcessor;
class FileStateCacheTests : public ::testing::Test
{
public:
void SetUp() override;
void TearDown() override;
void CheckForFile(QString path, bool shouldExist);
protected:
QTemporaryDir m_temporaryDir;
QDir m_temporarySourceDir;
AZStd::unique_ptr<FileStateBase> m_fileStateCache;
};
}
@@ -0,0 +1,158 @@
/*
* 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/JSON/rapidjson.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
#include <native/InternalBuilders/SettingsRegistryBuilder.h>
#include <native/tests/AssetProcessorTest.h>
namespace AssetProcessor
{
class SettingsRegistryBuilderTest
: public AssetProcessorTest
{
};
// These tests are done relative to "TestValues" because the Settings Registry adds runtime information for
// anything that is merged in.
TEST_F(SettingsRegistryBuilderTest, SettingsExporter_ExportRegistryToJson_ProducesIdenticalJsonToRegularWriter)
{
static constexpr char json[] =
R"( {
"TestValues":
{
"BoolTrue": true,
"BoolFalse": false,
"Integer": 42,
"Double": 42.0,
"String": "hello",
"Array": [ null, true, false, 42, 42.0, "hello", { "Field": 42 }, [ 42, 42.0 ] ]
}
})";
rapidjson::Document document;
document.Parse(json);
ASSERT_FALSE(document.HasParseError());
rapidjson::StringBuffer jsonOutputBuffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(jsonOutputBuffer);
document.FindMember("TestValues")->value.Accept(writer);
AZ::SettingsRegistryImpl registry;
ASSERT_TRUE(registry.MergeSettings(json, AZ::SettingsRegistryInterface::Format::JsonMergePatch));
rapidjson::StringBuffer registryOutputBuffer;
AZStd::vector<AZStd::string> excludes;
SettingsRegistryBuilder::SettingsExporter exporter(registryOutputBuffer, excludes);
registry.Visit(exporter, "/TestValues");
ASSERT_TRUE(exporter.Finalize());
EXPECT_EQ(jsonOutputBuffer.GetLength(), registryOutputBuffer.GetLength());
EXPECT_STREQ(jsonOutputBuffer.GetString(), registryOutputBuffer.GetString());
}
TEST_F(SettingsRegistryBuilderTest, SettingsExporter_FilterOutSection_FieldNotInOutput)
{
static constexpr char json[] =
R"( {
"TestValues":
{
"A":
{
"B":
{
"X": 42
},
"C": true
}
}
})";
AZ::SettingsRegistryImpl registry;
ASSERT_TRUE(registry.MergeSettings(json, AZ::SettingsRegistryInterface::Format::JsonMergePatch));
rapidjson::StringBuffer registryOutputBuffer;
AZStd::vector<AZStd::string> excludes;
excludes.push_back("/TestValues/A/B");
SettingsRegistryBuilder::SettingsExporter exporter(registryOutputBuffer, excludes);
registry.Visit(exporter, "/TestValues");
ASSERT_TRUE(exporter.Finalize());
rapidjson::Document document;
document.Parse(registryOutputBuffer.GetString(), registryOutputBuffer.GetLength());
ASSERT_FALSE(document.HasParseError());
auto it = document.FindMember("A");
ASSERT_NE(document.MemberEnd(), it);
EXPECT_EQ(it->value.MemberEnd(), it->value.FindMember("B"));
EXPECT_NE(it->value.MemberEnd(), it->value.FindMember("C"));
}
TEST_F(SettingsRegistryBuilderTest, SettingsExporter_ExportRegistryWithNull_NullIsSerialized)
{
static constexpr char json[] =
R"( [
{ "op": "add", "path": "/TestValues", "value": { "Null": null } }
])";
AZ::SettingsRegistryImpl registry;
ASSERT_TRUE(registry.MergeSettings(json, AZ::SettingsRegistryInterface::Format::JsonPatch));
rapidjson::StringBuffer registryOutputBuffer;
AZStd::vector<AZStd::string> excludes;
SettingsRegistryBuilder::SettingsExporter exporter(registryOutputBuffer, excludes);
registry.Visit(exporter, "/TestValues");
ASSERT_TRUE(exporter.Finalize());
rapidjson::Document document;
document.Parse(registryOutputBuffer.GetString(), registryOutputBuffer.GetLength());
ASSERT_FALSE(document.HasParseError());
auto it = document.FindMember("Null");
ASSERT_NE(document.MemberEnd(), it);
EXPECT_TRUE(it->value.IsNull());
}
TEST_F(SettingsRegistryBuilderTest, SettingsExporter_ExportCanBeReused_SecondExportWorksCorrectly)
{
static constexpr char jsonFirst[] =
R"( {
"TestValues": { "FirstPass" : 1 }
})";
static constexpr char jsonSecond[] =
R"( {
"TestValues": { "SecondPass" : 1 }
})";
AZ::SettingsRegistryImpl registryFirst;
ASSERT_TRUE(registryFirst.MergeSettings(jsonFirst, AZ::SettingsRegistryInterface::Format::JsonMergePatch));
AZ::SettingsRegistryImpl registrySecond;
ASSERT_TRUE(registrySecond.MergeSettings(jsonSecond, AZ::SettingsRegistryInterface::Format::JsonMergePatch));
rapidjson::StringBuffer registryOutputBuffer;
AZStd::vector<AZStd::string> excludes;
SettingsRegistryBuilder::SettingsExporter exporter(registryOutputBuffer, excludes);
registryFirst.Visit(exporter, "/TestValues");
ASSERT_TRUE(exporter.Finalize());
registryOutputBuffer.Clear();
exporter.Reset(registryOutputBuffer);
registrySecond.Visit(exporter, "/TestValues");
rapidjson::Document document;
document.Parse(jsonSecond);
ASSERT_FALSE(document.HasParseError());
rapidjson::StringBuffer jsonOutputBuffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(jsonOutputBuffer);
document.FindMember("TestValues")->value.Accept(writer);
EXPECT_EQ(jsonOutputBuffer.GetLength(), registryOutputBuffer.GetLength());
EXPECT_STREQ(jsonOutputBuffer.GetString(), registryOutputBuffer.GetString());
}
} // namespace AssetProcessor
@@ -0,0 +1,261 @@
/*
* 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 <native/tests/AssetProcessorTest.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <native/utilities/MissingDependencyScanner.h>
#include <AssetDatabase/AssetDatabase.h>
namespace AssetProcessor
{
class MissingDependencyScanner_Test
: public MissingDependencyScanner
{
public:
AZStd::unordered_map<AZStd::string, AZStd::vector<AZStd::string>>& GetDependenciesRulesMap()
{
return m_dependenciesRulesMap;
}
};
class MissingDependencyScannerTestsMockDatabaseLocationListener : public AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Handler
{
public:
MOCK_METHOD1(GetAssetDatabaseLocation, bool(AZStd::string&));
};
class MissingDependencyScannerTest
: public AssetProcessorTest
{
public:
MissingDependencyScannerTest()
{
}
protected:
void SetUp() override
{
using namespace testing;
using ::testing::NiceMock;
AssetProcessorTest::SetUp();
m_errorAbsorber = nullptr;
m_data = AZStd::make_unique<StaticData>();
QDir tempPath(m_data->m_tempDir.path());
m_data->m_databaseLocationListener.BusConnect();
m_data->m_databaseLocation = tempPath.absoluteFilePath("test_database.sqlite").toUtf8().constData();
ON_CALL(m_data->m_databaseLocationListener, GetAssetDatabaseLocation(_))
.WillByDefault(
DoAll( // set the 0th argument ref (string) to the database location and return true.
SetArgReferee<0>(m_data->m_databaseLocation),
Return(true)));
m_data->m_dbConn = AZStd::shared_ptr<AssetDatabaseConnection>(aznew AssetDatabaseConnection());
m_data->m_dbConn->OpenDatabase();
m_data->m_scopedDir.Setup(tempPath.absolutePath());
}
void TearDown() override
{
m_data = nullptr;
AssetProcessorTest::TearDown();
}
AZ::Outcome<AZ::s64, AZStd::string> CreateScanFolder(const AZStd::string& scanFolderName, const AZStd::string& scanFolderPath)
{
AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanFolder;
scanFolder.m_displayName = scanFolderName;
scanFolder.m_portableKey = scanFolderName;
scanFolder.m_scanFolder = scanFolderPath;
if (!m_data->m_dbConn->SetScanFolder(scanFolder))
{
return AZ::Failure(AZStd::string::format("Could not set create scan folder %s", scanFolderName.c_str()));
}
return AZ::Success(scanFolder.m_scanFolderID);
}
struct SourceAndProductInfo
{
AZ::Uuid m_uuid;
AZ::s64 m_productId;
};
AZ::Outcome<SourceAndProductInfo, AZStd::string> CreateSourceAndProductAsset(AZ::s64 scanFolderPK, const AZStd::string& sourceName, const AZStd::string& platform, const AZStd::string& productName)
{
using namespace AzToolsFramework::AssetDatabase;
SourceDatabaseEntry sourceEntry;
sourceEntry.m_sourceName = sourceName;
sourceEntry.m_sourceGuid = AssetUtilities::CreateSafeSourceUUIDFromName(sourceEntry.m_sourceName.c_str());
sourceEntry.m_scanFolderPK = scanFolderPK;
if (!m_data->m_dbConn->SetSource(sourceEntry))
{
return AZ::Failure(AZStd::string::format("Could not set source in the asset database for %s", sourceName.c_str()));
}
SourceAndProductInfo result;
result.m_uuid = sourceEntry.m_sourceGuid;
JobDatabaseEntry jobEntry;
jobEntry.m_sourcePK = sourceEntry.m_sourceID;
jobEntry.m_platform = platform;
jobEntry.m_jobRunKey = 1;
if(!m_data->m_dbConn->SetJob(jobEntry))
{
return AZ::Failure(AZStd::string::format("Could not set job in the asset database for %s", sourceName.c_str()));
}
ProductDatabaseEntry productEntry;
productEntry.m_jobPK = jobEntry.m_jobID;
productEntry.m_productName = AZStd::string::format("%s/%s", platform.c_str(), productName.c_str());
if(!m_data->m_dbConn->SetProduct(productEntry))
{
return AZ::Failure(AZStd::string::format("Could not set product in the asset database for %s", sourceName.c_str()));
}
result.m_productId = productEntry.m_productID;
return AZ::Success(result);
}
void CreateAndValidateMissingProductDependency(const AZStd::string& missingProductName)
{
using namespace AzToolsFramework::AssetDatabase;
QDir tempPath(m_data->m_tempDir.path());
QString testFilePath = tempPath.absoluteFilePath("subfolder1/assetProcessorManagerTest.txt");
AZStd::string testPlatform("pc");
AZStd::string missingProductPath(AZStd::string::format("test/%s", missingProductName.c_str()));
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(testFilePath, missingProductName.c_str()));
// Create the referenced product
AZ::Outcome<AZ::s64, AZStd::string> scanResult = CreateScanFolder("Test", tempPath.absoluteFilePath("subfolder1").toUtf8().constData());
ASSERT_TRUE(scanResult.IsSuccess());
AZ::s64 scanFolderIndex(scanResult.GetValue());
AZ::Outcome<SourceAndProductInfo, AZStd::string> firstAsset = CreateSourceAndProductAsset(scanFolderIndex, "tests/1", testPlatform, missingProductPath);
ASSERT_TRUE(firstAsset.IsSuccess());
AZ::Uuid actualTestGuid(firstAsset.GetValue().m_uuid);
// Create the product that references the product above. This represents the dummy file we created up above
AZ::Outcome<SourceAndProductInfo, AZStd::string> secondAsset = CreateSourceAndProductAsset(scanFolderIndex, "tests/2", testPlatform, "test/tests/2.product");
ASSERT_TRUE(secondAsset.IsSuccess());
AZ::s64 productId = secondAsset.GetValue().m_productId;
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer container;
m_data->m_scanner.ScanFile(testFilePath.toUtf8().constData(), AssetProcessor::MissingDependencyScanner::DefaultMaxScanIteration, productId, container, m_data->m_dbConn, false, [](AZStd::string /*dependencyFile*/) {});
MissingProductDependencyDatabaseEntryContainer missingDeps;
ASSERT_TRUE(m_data->m_dbConn->GetMissingProductDependenciesByProductId(productId, missingDeps));
ASSERT_EQ(missingDeps.size(), 1);
ASSERT_EQ(missingDeps[0].m_productPK, productId);
ASSERT_EQ(missingDeps[0].m_dependencySourceGuid, actualTestGuid);
}
struct StaticData
{
QTemporaryDir m_tempDir;
AZStd::string m_databaseLocation;
::testing::NiceMock<MissingDependencyScannerTestsMockDatabaseLocationListener> m_databaseLocationListener;
AZStd::shared_ptr<AssetDatabaseConnection> m_dbConn;
MissingDependencyScanner_Test m_scanner;
UnitTestUtils::ScopedDir m_scopedDir; // Sets up FileIO instance
};
AZStd::unique_ptr<StaticData> m_data;
};
TEST_F(MissingDependencyScannerTest, ScanFile_FindsValidReferenceToProduct)
{
CreateAndValidateMissingProductDependency("tests/1.product");
}
TEST_F(MissingDependencyScannerTest, ScanFile_ValidReferenceToFileWithDash_FindsMissingReference)
{
CreateAndValidateMissingProductDependency("tests/1-withdash.product");
}
TEST_F(MissingDependencyScannerTest, ScanFile_CPP_File_FindsValidReferenceToProduct)
{
using namespace AzToolsFramework::AssetDatabase;
QDir tempPath(m_data->m_tempDir.path());
// Create the referenced product
ScanFolderDatabaseEntry scanFolder;
scanFolder.m_displayName = "Test";
scanFolder.m_portableKey = "Test";
scanFolder.m_scanFolder = tempPath.absoluteFilePath("subfolder1").toUtf8().constData();
ASSERT_TRUE(m_data->m_dbConn->SetScanFolder(scanFolder));
SourceDatabaseEntry sourceEntry;
sourceEntry.m_sourceName = "tests/1.source";
sourceEntry.m_sourceGuid = AssetUtilities::CreateSafeSourceUUIDFromName(sourceEntry.m_sourceName.c_str());
sourceEntry.m_scanFolderPK = 1;
ASSERT_TRUE(m_data->m_dbConn->SetSource(sourceEntry));
JobDatabaseEntry jobEntry;
jobEntry.m_sourcePK = sourceEntry.m_sourceID;
jobEntry.m_platform = "pc";
jobEntry.m_jobRunKey = 1;
ASSERT_TRUE(m_data->m_dbConn->SetJob(jobEntry));
ProductDatabaseEntry productEntry;
productEntry.m_jobPK = jobEntry.m_jobID;
productEntry.m_productName = "pc/test/tests/1.product";
ASSERT_TRUE(m_data->m_dbConn->SetProduct(productEntry));
AZStd::string productReference("tests/1.product");
// Create a cpp file that references the product above.
QString sourceFilePath = tempPath.absoluteFilePath("subfolder1/TestFile.cpp");
AZStd::string codeSourceCode = AZStd::string::format(R"(#include <Dummy/Dummy.h>;
#define PRODUCT_REFERENCE "%s")", productReference.c_str());
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(sourceFilePath, codeSourceCode.c_str()));
AZStd::string productDependency;
auto missingDependencyCallback = [&](AZStd::string relativeDependencyFilePath)
{
productDependency = relativeDependencyFilePath;
};
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer container;
AZStd::string dependencyToken = "dummy";
// Since dependency rule map is empty this should show a missing dependency
m_data->m_scanner.ScanFile(sourceFilePath.toUtf8().constData(), AssetProcessor::MissingDependencyScanner::DefaultMaxScanIteration, m_data->m_dbConn, dependencyToken, false, missingDependencyCallback);
ASSERT_EQ(productDependency, productReference);
productDependency.clear();
QString anotherSourceFilePath = tempPath.absoluteFilePath("subfolder1/TestFile.cpp");
codeSourceCode = AZStd::string::format(R"(#include <Dummy/Dummy.h>;
AZStd::string filePath("%s")", productReference.c_str());
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(anotherSourceFilePath, codeSourceCode.c_str()));
m_data->m_scanner.ScanFile(anotherSourceFilePath.toUtf8().constData(), AssetProcessor::MissingDependencyScanner::DefaultMaxScanIteration, m_data->m_dbConn, dependencyToken, false, missingDependencyCallback);
ASSERT_EQ(productDependency, productReference);
AZStd::vector<AZStd::string> rulesMap;
rulesMap.emplace_back("*.product");
m_data->m_scanner.GetDependenciesRulesMap()[dependencyToken] = rulesMap;
productDependency.clear();
m_data->m_scanner.ScanFile(sourceFilePath.toUtf8().constData(), AssetProcessor::MissingDependencyScanner::DefaultMaxScanIteration, m_data->m_dbConn, dependencyToken, false, missingDependencyCallback);
ASSERT_TRUE(productDependency.empty());
}
}
@@ -0,0 +1,233 @@
/*
* 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 <QTemporaryDir>
#include <AzTest/AzTest.h>
#include <AzCore/UnitTest/TestTypes.h>
#include "AzToolsFramework/API/AssetDatabaseBus.h"
#include "AssetDatabase/AssetDatabase.h"
#include <AssetManager/PathDependencyManager.h>
namespace UnitTests
{
class MockDatabaseLocationListener : public AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Handler
{
public:
MOCK_METHOD1(GetAssetDatabaseLocation, bool(AZStd::string&));
};
namespace Util
{
using namespace AzToolsFramework::AssetDatabase;
void CreateSourceJobAndProduct(AssetProcessor::AssetDatabaseConnection* stateData, AZ::s64 scanfolderPk, SourceDatabaseEntry& source, JobDatabaseEntry& job, ProductDatabaseEntry& product, const char* sourceName, const char* productName)
{
source = SourceDatabaseEntry(scanfolderPk, sourceName, AZ::Uuid::CreateRandom(), "fingerprint");
EXPECT_TRUE(stateData->SetSource(source));
job = JobDatabaseEntry(source.m_sourceID, "jobkey", 1111, "pc", AZ::Uuid::CreateRandom(), AzToolsFramework::AssetSystem::JobStatus::Completed, 4444);
EXPECT_TRUE(stateData->SetJob(job));
product = ProductDatabaseEntry(job.m_jobID, 0, productName, AZ::Data::AssetType::CreateRandom());
EXPECT_TRUE(stateData->SetProduct(product));
}
}
struct PathDependencyDeletionTest
: UnitTest::ScopedAllocatorSetupFixture
, UnitTest::TraceBusRedirector
{
void SetUp() override;
void TearDown() override;
QTemporaryDir m_tempDir;
AZStd::string m_databaseLocation;
::testing::NiceMock<MockDatabaseLocationListener> m_databaseLocationListener;
AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection> m_stateData;
AZStd::unique_ptr<AssetProcessor::PlatformConfiguration> m_platformConfig;
};
void PathDependencyDeletionTest::SetUp()
{
using namespace ::testing;
using namespace AzToolsFramework::AssetDatabase;
BusConnect();
QDir tempPath(m_tempDir.path());
m_databaseLocationListener.BusConnect();
// in other unit tests we may open the database called ":memory:" to use an in-memory database instead of one on disk.
// in this test, however, we use a real database, because the file processor shares it and opens its own connection to it.
// ":memory:" databases are one-instance-only, and even if another connection is opened to ":memory:" it would
// not share with others created using ":memory:" and get a unique database instead.
m_databaseLocation = tempPath.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),
Return(true)));
m_stateData = AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection>(new AssetProcessor::AssetDatabaseConnection());
m_stateData->OpenDatabase();
m_platformConfig = AZStd::make_unique<AssetProcessor::PlatformConfiguration>();
}
void PathDependencyDeletionTest::TearDown()
{
BusDisconnect();
}
TEST_F(PathDependencyDeletionTest, ExistingSourceWithUnmetDependency_RemovedFromDB_DependentSourceCreatedWithoutError)
{
using namespace AzToolsFramework::AssetDatabase;
// Add a product to the db with an unmet dependency
ScanFolderDatabaseEntry scanFolder("folder", "test", "test", "");
m_stateData->SetScanFolder(scanFolder);
SourceDatabaseEntry source1, source2;
JobDatabaseEntry job1, job2;
ProductDatabaseEntry product1, product2;
Util::CreateSourceJobAndProduct(m_stateData.get(), scanFolder.m_scanFolderID, source1, job1, product1, "source1.txt", "product1.jpg");
ProductDependencyDatabaseEntry dependency(product1.m_productID, AZ::Uuid::CreateRandom(), 0, 0, "pc", 0, "source2.txt", ProductDependencyDatabaseEntry::DependencyType::ProductDep_SourceFile);
m_stateData->SetProductDependency(dependency);
AssetProcessor::PathDependencyManager manager(m_stateData, m_platformConfig.get());
// Delete the data from the database
m_stateData->RemoveSource(source1.m_sourceID);
Util::CreateSourceJobAndProduct(m_stateData.get(), scanFolder.m_scanFolderID, source2, job2, product2, "source2.txt", "product2.jpg");
manager.RetryDeferredDependencies(source2);
}
TEST_F(PathDependencyDeletionTest, ExistingSourceWithUnmetDependency_RemovedFromDB_DependentProductCreatedWithoutError)
{
using namespace AzToolsFramework::AssetDatabase;
// Add a product to the db with an unmet dependency
ScanFolderDatabaseEntry scanFolder("folder", "test", "test", "");
m_stateData->SetScanFolder(scanFolder);
SourceDatabaseEntry source1, source2;
JobDatabaseEntry job1, job2;
ProductDatabaseEntry product1, product2;
Util::CreateSourceJobAndProduct(m_stateData.get(), scanFolder.m_scanFolderID, source1, job1, product1, "source1.txt", "product1.jpg");
ProductDependencyDatabaseEntry dependency(product1.m_productID, AZ::Uuid::CreateRandom(), 0, 0, "pc", 0, "product2.jpg", ProductDependencyDatabaseEntry::DependencyType::ProductDep_ProductFile);
m_stateData->SetProductDependency(dependency);
AssetProcessor::PathDependencyManager manager(m_stateData, m_platformConfig.get());
// Delete the data from the database
m_stateData->RemoveSource(source1.m_sourceID);
Util::CreateSourceJobAndProduct(m_stateData.get(), scanFolder.m_scanFolderID, source2, job2, product2, "source2.txt", "product2.jpg");
manager.RetryDeferredDependencies(source2);
}
TEST_F(PathDependencyDeletionTest, NewSourceWithUnmetDependency_RemovedFromDB_DependentSourceCreatedWithoutError)
{
using namespace AzToolsFramework::AssetDatabase;
AssetProcessor::PathDependencyManager manager(m_stateData, m_platformConfig.get());
// Add a product to the db with an unmet dependency
ScanFolderDatabaseEntry scanFolder("folder", "test", "test", "");
m_stateData->SetScanFolder(scanFolder);
SourceDatabaseEntry source1, source2;
JobDatabaseEntry job1, job2;
ProductDatabaseEntry product1, product2;
Util::CreateSourceJobAndProduct(m_stateData.get(), scanFolder.m_scanFolderID, source1, job1, product1, "source1.txt", "product1.jpg");
AssetBuilderSDK::ProductPathDependencySet set;
set.insert(AssetBuilderSDK::ProductPathDependency("source2.txt", AssetBuilderSDK::ProductPathDependencyType::SourceFile));
manager.SaveUnresolvedDependenciesToDatabase(set, product1, "pc");
// Delete the data from the database
m_stateData->RemoveSource(source1.m_sourceID);
Util::CreateSourceJobAndProduct(m_stateData.get(), scanFolder.m_scanFolderID, source2, job2, product2, "source2.txt", "product2.jpg");
manager.RetryDeferredDependencies(source2);
}
TEST_F(PathDependencyDeletionTest, NewSourceWithUnmetDependency_RemovedFromDB_DependentProductCreatedWithoutError)
{
using namespace AzToolsFramework::AssetDatabase;
AssetProcessor::PathDependencyManager manager(m_stateData, m_platformConfig.get());
// Add a product to the db with an unmet dependency
ScanFolderDatabaseEntry scanFolder("folder", "test", "test", "");
m_stateData->SetScanFolder(scanFolder);
SourceDatabaseEntry source1, source2;
JobDatabaseEntry job1, job2;
ProductDatabaseEntry product1, product2;
Util::CreateSourceJobAndProduct(m_stateData.get(), scanFolder.m_scanFolderID, source1, job1, product1, "source1.txt", "product1.jpg");
AssetBuilderSDK::ProductPathDependencySet set;
set.insert(AssetBuilderSDK::ProductPathDependency("product2.jpg", AssetBuilderSDK::ProductPathDependencyType::ProductFile));
manager.SaveUnresolvedDependenciesToDatabase(set, product1, "pc");
// Delete the data from the database
m_stateData->RemoveSource(source1.m_sourceID);
Util::CreateSourceJobAndProduct(m_stateData.get(), scanFolder.m_scanFolderID, source2, job2, product2, "source2.txt", "product2.jpg");
manager.RetryDeferredDependencies(source2);
}
TEST_F(PathDependencyDeletionTest, NewSourceWithUnmetDependency_Wildcard_RemovedFromDB_DependentSourceCreatedWithoutError)
{
using namespace AzToolsFramework::AssetDatabase;
AssetProcessor::PathDependencyManager manager(m_stateData, m_platformConfig.get());
// Add a product to the db with an unmet dependency
ScanFolderDatabaseEntry scanFolder("folder", "test", "test", "");
m_stateData->SetScanFolder(scanFolder);
SourceDatabaseEntry source1, source2;
JobDatabaseEntry job1, job2;
ProductDatabaseEntry product1, product2;
Util::CreateSourceJobAndProduct(m_stateData.get(), scanFolder.m_scanFolderID, source1, job1, product1, "source1.txt", "product1.jpg");
AssetBuilderSDK::ProductPathDependencySet set;
set.insert(AssetBuilderSDK::ProductPathDependency("sou*ce2.txt", AssetBuilderSDK::ProductPathDependencyType::SourceFile));
manager.SaveUnresolvedDependenciesToDatabase(set, product1, "pc");
// Delete the data from the database
m_stateData->RemoveSource(source1.m_sourceID);
Util::CreateSourceJobAndProduct(m_stateData.get(), scanFolder.m_scanFolderID, source2, job2, product2, "source2.txt", "product2.jpg");
manager.RetryDeferredDependencies(source2);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,443 @@
/*
* 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 "assetBuilderSDKTest.h"
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/RTTI/AttributeReader.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Script/ScriptContextAttributes.h>
#include <AzCore/UnitTest/TestTypes.h>
#include "native/tests/BaseAssetProcessorTest.h"
#include "native/unittests/UnitTestRunner.h"
namespace AssetProcessor
{
struct AssetBehaviorContextTest
: public ::testing::Test
{
struct DataMembers
{
UnitTestUtils::AssertAbsorber m_absorber;
DataMembers() = default;
};
// the component application creates and returns a system entity, but doesn't keep track of it
AZ::Entity* m_systemEntity = nullptr;
// store all data we create here so that it can be destroyed on shutdown before we remove allocators
DataMembers* m_data = nullptr;
// the app is created separately so that we can control its lifetime.
AZStd::unique_ptr<AZ::ComponentApplication> m_app;
void SetUp() override
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
m_app.reset(aznew AZ::ComponentApplication());
AZ::ComponentApplication::Descriptor desc;
m_systemEntity = m_app->Create(desc);
AssetBuilderSDK::InitializeSerializationContext();
AssetBuilderSDK::InitializeBehaviorContext();
m_data = azcreate(DataMembers, ());
}
void TearDown() override
{
EXPECT_EQ(0, m_data->m_absorber.m_numAssertsAbsorbed);
EXPECT_EQ(0, m_data->m_absorber.m_numErrorsAbsorbed);
EXPECT_EQ(0, m_data->m_absorber.m_numWarningsAbsorbed);
azdestroy(m_data);
delete m_systemEntity;
m_systemEntity = nullptr;
m_app->Destroy();
m_app.reset();
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
bool IsBehaviorFlaggedForEditor(const AZ::AttributeArray& attributes)
{
AZ::Script::Attributes::ScopeFlags scopeType = AZ::Script::Attributes::ScopeFlags::Launcher;
AZ::Attribute* scopeAttribute = AZ::FindAttribute(AZ::Script::Attributes::Scope, attributes);
if (scopeAttribute)
{
AZ::AttributeReader scopeAttributeReader(nullptr, scopeAttribute);
scopeAttributeReader.Read<AZ::Script::Attributes::ScopeFlags>(scopeType);
}
return (scopeType == AZ::Script::Attributes::ScopeFlags::Automation ||
scopeType == AZ::Script::Attributes::ScopeFlags::Common);
}
};
TEST_F(AssetBehaviorContextTest, DetectBehaviorAssetBuilderPattern)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("AssetBuilderPattern");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("type"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("pattern"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("Regex"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("Wildcard"));
EXPECT_EQ(1, behaviorClass->m_constructors.size());
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorContext->m_classes.find("AZStd::vector<AssetBuilderPattern, allocator>"));
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorJobDescriptor)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("JobDescriptor");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_methods.end() != behaviorClass->m_methods.find("set_platform_identifier"));
EXPECT_TRUE(behaviorClass->m_methods.end() != behaviorClass->m_methods.find("get_platform_identifier"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("jobParameters"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("additionalFingerprintInfo"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("priority"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("checkExclusiveLock"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("checkServer"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("jobDependencyList"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("failOnError"));
EXPECT_EQ(2, behaviorClass->m_constructors.size());
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorContext->m_classes.find("AZStd::vector<JobDescriptor, allocator>"));
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorProductDependency)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("ProductDependency");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("dependencyId"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("flags"));
EXPECT_EQ(1, behaviorClass->m_constructors.size());
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorContext->m_classes.find("AZStd::vector<ProductDependency, allocator>"));
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorJobProduct)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("JobProduct");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("productFileName"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("productAssetType"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("productSubID"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("productDependencies"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("pathDependencies"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("dependenciesHandled"));
EXPECT_EQ(2, behaviorClass->m_constructors.size());
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorContext->m_classes.find("AZStd::vector<JobProduct, allocator>"));
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorProcessJobRequest)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("ProcessJobRequest");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("sourceFile"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("watchFolder"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("fullPath"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("builderGuid"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("jobDescription"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("tempDirPath"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("platformInfo"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("sourceFileDependencyList"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("sourceFileUUID"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("jobId"));
EXPECT_EQ(0, behaviorClass->m_constructors.size());
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorContext->m_classes.find("AZStd::vector<SourceFileDependency, allocator>"));
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorSourceFileDependency)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("SourceFileDependency");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("sourceFileDependencyPath"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("sourceFileDependencyUUID"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("sourceDependencyType"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("Absolute"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("Wildcards"));
EXPECT_EQ(2, behaviorClass->m_constructors.size());
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorAssetBuilderDesc)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("AssetBuilderDesc");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("analysisFingerprint"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("busId"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("flags"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("name"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("patterns"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("version"));
EXPECT_EQ(1, behaviorClass->m_constructors.size());
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorCreateJobsResponse)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("CreateJobsResponse");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("result"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("sourceFileDependencyList"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("createJobOutputs"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("ResultFailed"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("ResultShuttingDown"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("ResultSuccess"));
EXPECT_EQ(0, behaviorClass->m_constructors.size());
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorCreateJobsRequest)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("CreateJobsRequest");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("builderId"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("watchFolder"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("sourceFile"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("sourceFileUUID"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("enabledPlatforms"));
EXPECT_EQ(0, behaviorClass->m_constructors.size());
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorProductPathDependency)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("ProductPathDependency");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("dependencyPath"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("dependencyType"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("ProductFile"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("SourceFile"));
EXPECT_EQ(0, behaviorClass->m_constructors.size());
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorContext->m_classes.find("AZStd::vector<ProductPathDependency, allocator>"));
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorProcessJobResponse)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("ProcessJobResponse");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("outputProducts"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("resultCode"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("requiresSubIdGeneration"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("sourcesToReprocess"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("Success"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("Failed"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("Crashed"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("Cancelled"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("NetworkIssue"));
EXPECT_EQ(0, behaviorClass->m_constructors.size());
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorRegisterBuilderResponse)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("RegisterBuilderResponse");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("assetBuilderDescList"));
EXPECT_EQ(1, behaviorClass->m_constructors.size());
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorContext->m_classes.find("AZStd::vector<AssetBuilderDesc, allocator>"));
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorRegisterBuilderRequest)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("RegisterBuilderRequest");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("filePath"));
EXPECT_EQ(0, behaviorClass->m_constructors.size());
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorJobDependency)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("JobDependency");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("sourceFile"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("jobKey"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("platformIdentifier"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("type"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("Fingerprint"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("Order"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("OrderOnce"));
EXPECT_EQ(0, behaviorClass->m_constructors.size());
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorContext->m_classes.find("AZStd::vector<JobDependency, allocator>"));
}
TEST_F(AssetBehaviorContextTest, DetectBehaviorPlatformInfo)
{
auto&& behaviorContext = m_app->GetBehaviorContext();
auto behaviorClassEntry = behaviorContext->m_classes.find("PlatformInfo");
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorClassEntry);
AZ::BehaviorClass* behaviorClass = behaviorClassEntry->second;
EXPECT_TRUE(IsBehaviorFlaggedForEditor(behaviorClass->m_attributes));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("identifier"));
EXPECT_TRUE(behaviorClass->m_properties.end() != behaviorClass->m_properties.find("tags"));
EXPECT_EQ(0, behaviorClass->m_constructors.size());
EXPECT_TRUE(behaviorContext->m_classes.end() != behaviorContext->m_classes.find("AZStd::vector<PlatformInfo, allocator>"));
}
template <typename T>
bool EnumClassReadUpdateTest(AZ::BehaviorProperty* behaviorProperty, AZ::BehaviorObject& instance, T value)
{
T enumClassTypeValue = {};
EXPECT_TRUE(behaviorProperty->m_setter->Invoke(instance, value));
EXPECT_TRUE(behaviorProperty->m_getter->InvokeResult(enumClassTypeValue, instance));
EXPECT_EQ(value, enumClassTypeValue);
return value == enumClassTypeValue;
}
TEST_F(AssetBehaviorContextTest, DISABLED_EnumClass_ProductPathDependencyType_Accessible)
{
using namespace AssetBuilderSDK;
auto&& behaviorContext = m_app->GetBehaviorContext();
auto&& behaviorClassEntry = behaviorContext->m_classes.find("ProductPathDependency");
auto&& behaviorClass = behaviorClassEntry->second;
auto&& behaviorProperty = behaviorClass->m_properties["dependencyType"];
auto instance = behaviorClass->Create();
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, ProductPathDependencyType::ProductFile));
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, ProductPathDependencyType::SourceFile));
behaviorClass->Destroy(instance);
}
TEST_F(AssetBehaviorContextTest, DISABLED_EnumClass_AssetBuilderPatternPatternType_Accessible)
{
using namespace AssetBuilderSDK;
auto&& behaviorContext = m_app->GetBehaviorContext();
auto&& behaviorClassEntry = behaviorContext->m_classes.find("AssetBuilderPattern");
auto&& behaviorClass = behaviorClassEntry->second;
auto&& behaviorProperty = behaviorClass->m_properties["type"];
auto instance = behaviorClass->Create();
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, AssetBuilderPattern::PatternType::Wildcard));
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, AssetBuilderPattern::PatternType::Regex));
behaviorClass->Destroy(instance);
}
TEST_F(AssetBehaviorContextTest, DISABLED_EnumClass_ProcessJobResponse_Accessible)
{
using namespace AssetBuilderSDK;
auto&& behaviorContext = m_app->GetBehaviorContext();
auto&& behaviorClassEntry = behaviorContext->m_classes.find("ProcessJobResponse");
auto&& behaviorClass = behaviorClassEntry->second;
auto&& behaviorProperty = behaviorClass->m_properties["resultCode"];
auto instance = behaviorClass->Create();
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, ProcessJobResultCode::ProcessJobResult_Success));
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, ProcessJobResultCode::ProcessJobResult_Failed));
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, ProcessJobResultCode::ProcessJobResult_Crashed));
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, ProcessJobResultCode::ProcessJobResult_Cancelled));
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, ProcessJobResultCode::ProcessJobResult_NetworkIssue));
behaviorClass->Destroy(instance);
}
TEST_F(AssetBehaviorContextTest, DISABLED_EnumClass_JobDependencyType_Accessible)
{
using namespace AssetBuilderSDK;
auto&& behaviorContext = m_app->GetBehaviorContext();
auto&& behaviorClassEntry = behaviorContext->m_classes.find("JobDependency");
auto&& behaviorClass = behaviorClassEntry->second;
auto&& behaviorProperty = behaviorClass->m_properties["type"];
auto instance = behaviorClass->Create();
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, JobDependencyType::Fingerprint));
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, JobDependencyType::Order));
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, JobDependencyType::OrderOnce));
behaviorClass->Destroy(instance);
}
TEST_F(AssetBehaviorContextTest, DISABLED_EnumClass_CreateJobsResultCode_Accessible)
{
using namespace AssetBuilderSDK;
auto&& behaviorContext = m_app->GetBehaviorContext();
auto&& behaviorClassEntry = behaviorContext->m_classes.find("CreateJobsResponse");
auto&& behaviorClass = behaviorClassEntry->second;
auto&& behaviorProperty = behaviorClass->m_properties["result"];
auto instance = behaviorClass->Create();
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, CreateJobsResultCode::Failed));
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, CreateJobsResultCode::ShuttingDown));
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, CreateJobsResultCode::Success));
behaviorClass->Destroy(instance);
}
TEST_F(AssetBehaviorContextTest, DISABLED_EnumClass_SourceFileDependency_Accessible)
{
using namespace AssetBuilderSDK;
auto&& behaviorContext = m_app->GetBehaviorContext();
auto&& behaviorClassEntry = behaviorContext->m_classes.find("SourceFileDependency");
auto&& behaviorClass = behaviorClassEntry->second;
auto&& behaviorProperty = behaviorClass->m_properties["sourceDependencyType"];
auto instance = behaviorClass->Create();
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, SourceFileDependency::SourceFileDependencyType::Absolute));
EXPECT_TRUE(EnumClassReadUpdateTest(behaviorProperty, instance, SourceFileDependency::SourceFileDependencyType::Wildcards));
behaviorClass->Destroy(instance);
}
};
@@ -0,0 +1,295 @@
/*
* 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 <AssetBuilderSDK/SerializationDependencies.h>
#include <Tests/SerializeContextFixture.h>
namespace SerializationDependencyTests
{
class ClassWithAssetId
{
public:
AZ_RTTI(ClassWithAssetId, "{F6970E05-890B-4E5D-A944-1F58E9751922}");
virtual ~ClassWithAssetId() {}
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<ClassWithAssetId>()
->Field("m_assetId", &ClassWithAssetId::m_assetId);
}
}
AZ::Data::AssetId m_assetId;
};
class ClassWithAsset
{
public:
AZ_RTTI(ClassWithAsset, "{D2BCF9BF-3E64-4942-8AFB-BD3E8453CB52}");
virtual ~ClassWithAsset() {}
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<ClassWithAsset>()
->Field("m_asset", &ClassWithAsset::m_asset);
}
}
AZ::Data::Asset<AZ::Data::AssetData> m_asset;
};
class ClassWithNoLoadAsset
{
public:
AZ_RTTI(ClassWithNoLoadAsset, "{C38D0DFA-A19E-48EF-BC0E-2BE4E320F65A}");
ClassWithNoLoadAsset() : m_asset(AZ::Data::AssetLoadBehavior::NoLoad)
{
}
virtual ~ClassWithNoLoadAsset() {}
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<ClassWithNoLoadAsset>()
->Field("m_asset", &ClassWithNoLoadAsset::m_asset);
}
}
AZ::Data::Asset<AZ::Data::AssetData> m_asset;
};
class SimpleAssetMock : public AzFramework::SimpleAssetReferenceBase
{
public:
AZ_RTTI(SimpleAssetMock, "{AA2CDA39-A357-441D-BABA-B1AD3C3A8083}", AzFramework::SimpleAssetReferenceBase);
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<SimpleAssetMock, AzFramework::SimpleAssetReferenceBase>();
}
}
AZ::Data::AssetType GetAssetType() const override
{
// Use an arbitrary ID for the asset type.
return AZ::Data::AssetType("{03FD33E2-DA2F-4021-A266-0DC9714FF84D}");
}
virtual const char* GetFileFilter() const
{
return nullptr;
}
};
class ClassWithSimpleAsset
{
public:
AZ_RTTI(ClassWithSimpleAsset, "{F4F50653-692C-46F8-A9B0-73C19523E56A}");
virtual ~ClassWithSimpleAsset() {}
static void Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<ClassWithSimpleAsset>()
->Field("m_simpleAsset", &ClassWithSimpleAsset::m_simpleAsset);
}
}
SimpleAssetMock m_simpleAsset;
};
class SerializationDependenciesTests
: public UnitTest::SerializeContextFixture
, public UnitTest::TraceBusRedirector
{
protected:
void SetUp() override
{
SerializeContextFixture::SetUp();
AZ::Debug::TraceMessageBus::Handler::BusConnect();
AZ::Data::AssetId::Reflect(m_serializeContext);
AZ::Data::AssetData::Reflect(m_serializeContext);
AzFramework::SimpleAssetReferenceBase::Reflect(m_serializeContext);
ClassWithAssetId::Reflect(m_serializeContext);
ClassWithAsset::Reflect(m_serializeContext);
SimpleAssetMock::Reflect(m_serializeContext);
ClassWithSimpleAsset::Reflect(m_serializeContext);
ClassWithNoLoadAsset::Reflect(m_serializeContext);
}
void TearDown() override
{
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
UnitTest::SerializeContextFixture::TearDown();
}
};
int GetProductDependencySlot(const AZStd::vector<AssetBuilderSDK::ProductDependency>& productDependencies, const AZ::Data::AssetId& assetId)
{
for (int productDependencySlot = 0; productDependencySlot < aznumeric_cast<int>(productDependencies.size()); ++productDependencySlot)
{
if (productDependencies[productDependencySlot].m_dependencyId == assetId)
{
return productDependencySlot;
}
}
return false;
}
bool FindAssetIdInProductDependencies(const AZStd::vector<AssetBuilderSDK::ProductDependency>& productDependencies, const AZ::Data::AssetId& assetId)
{
return (GetProductDependencySlot(productDependencies, assetId) != -1);
}
TEST_F(SerializationDependenciesTests, GatherProductDependencies_NullData_NoCrash)
{
AZStd::vector<AssetBuilderSDK::ProductDependency> productDependencies;
AssetBuilderSDK::ProductPathDependencySet productPathDependencySet;
// Using a known type for the nullptr instead of a void* so the template resolves properly for the call.
ClassWithAssetId* nullClass = nullptr;
AZ_TEST_START_TRACE_SUPPRESSION;
bool gatherResult = AssetBuilderSDK::GatherProductDependencies(*m_serializeContext, nullClass, productDependencies, productPathDependencySet);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
ASSERT_FALSE(gatherResult);
ASSERT_EQ(productDependencies.size(), 0);
ASSERT_EQ(productPathDependencySet.size(), 0);
}
TEST_F(SerializationDependenciesTests, GatherProductDependencies_HasValidAssetId_AssetIdFound)
{
AZStd::vector<AssetBuilderSDK::ProductDependency> productDependencies;
AssetBuilderSDK::ProductPathDependencySet productPathDependencySet;
ClassWithAssetId classWithAssetId;
classWithAssetId.m_assetId = AZ::Data::AssetId("{3008D6F9-1E56-4699-95F9-91A3758A964E}", 33);
bool gatherResult = AssetBuilderSDK::GatherProductDependencies(*m_serializeContext, &classWithAssetId, productDependencies, productPathDependencySet);
ASSERT_TRUE(gatherResult);
ASSERT_EQ(productDependencies.size(), 1);
ASSERT_TRUE(FindAssetIdInProductDependencies(productDependencies, classWithAssetId.m_assetId));
ASSERT_EQ(productPathDependencySet.size(), 0);
}
TEST_F(SerializationDependenciesTests, GatherProductDependencies_HasNullAssetId_NoDependencyEmitted)
{
AZStd::vector<AssetBuilderSDK::ProductDependency> productDependencies;
AssetBuilderSDK::ProductPathDependencySet productPathDependencySet;
ClassWithAssetId classWithAssetId;
bool gatherResult = AssetBuilderSDK::GatherProductDependencies(*m_serializeContext, &classWithAssetId, productDependencies, productPathDependencySet);
ASSERT_TRUE(gatherResult);
ASSERT_EQ(productDependencies.size(), 0);
ASSERT_EQ(productPathDependencySet.size(), 0);
}
TEST_F(SerializationDependenciesTests, GatherProductDependencies_HasValidAsset_AssetIdFound)
{
AZStd::vector<AssetBuilderSDK::ProductDependency> productDependencies;
AssetBuilderSDK::ProductPathDependencySet productPathDependencySet;
ClassWithAsset classWithAsset;
AZ::Data::AssetId testAssetId("{CAAC5458-0738-43F6-A2BD-4E315C64BFD3}", 71);
classWithAsset.m_asset = AZ::Data::Asset<AZ::Data::AssetData>(
testAssetId,
azrtti_typeid<AZ::Data::AssetData>());
bool gatherResult = AssetBuilderSDK::GatherProductDependencies(*m_serializeContext, &classWithAsset, productDependencies, productPathDependencySet);
ASSERT_TRUE(gatherResult);
ASSERT_EQ(productDependencies.size(), 1);
ASSERT_TRUE(FindAssetIdInProductDependencies(productDependencies, testAssetId));
ASSERT_EQ(productPathDependencySet.size(), 0);
}
TEST_F(SerializationDependenciesTests, GatherProductDependencies_HasNullAsset_NoDependencyEmitted)
{
AZStd::vector<AssetBuilderSDK::ProductDependency> productDependencies;
AssetBuilderSDK::ProductPathDependencySet productPathDependencySet;
ClassWithAsset classWithAsset;
AZ::Data::AssetId testAssetId;
testAssetId.SetInvalid(); // Make it clear that this is an invalid ID.
classWithAsset.m_asset = AZ::Data::Asset<AZ::Data::AssetData>(
testAssetId,
azrtti_typeid<AZ::Data::AssetData>());
bool gatherResult = AssetBuilderSDK::GatherProductDependencies(*m_serializeContext, &classWithAsset, productDependencies, productPathDependencySet);
ASSERT_TRUE(gatherResult);
ASSERT_EQ(productDependencies.size(), 0);
ASSERT_EQ(productPathDependencySet.size(), 0);
}
TEST_F(SerializationDependenciesTests, GatherProductDependencies_HasValidSimpleAsset_AssetPathFound)
{
AZStd::vector<AssetBuilderSDK::ProductDependency> productDependencies;
AssetBuilderSDK::ProductPathDependencySet productPathDependencySet;
ClassWithSimpleAsset classWithSimpleAsset;
const AZStd::string expectedAssetPath("TestAssetPathString.txt");
classWithSimpleAsset.m_simpleAsset.SetAssetPath(expectedAssetPath.c_str());
bool gatherResult = AssetBuilderSDK::GatherProductDependencies(*m_serializeContext, &classWithSimpleAsset, productDependencies, productPathDependencySet);
ASSERT_TRUE(gatherResult);
ASSERT_EQ(productDependencies.size(), 0);
ASSERT_EQ(productPathDependencySet.size(), 1);
ASSERT_TRUE(productPathDependencySet.begin()->m_dependencyPath.compare(expectedAssetPath) == 0);
ASSERT_TRUE(productPathDependencySet.begin()->m_dependencyType == AssetBuilderSDK::ProductPathDependencyType::ProductFile);
}
TEST_F(SerializationDependenciesTests, GatherProductDependencies_DependencyFlagsSerialization_Success)
{
AZStd::vector<AssetBuilderSDK::ProductDependency> productDependencies;
AssetBuilderSDK::ProductPathDependencySet productPathDependencySet;
ClassWithNoLoadAsset classWithNoLoadAsset;
AZ::Data::AssetId testAssetId("{CAAC5458-0738-43F6-A2BD-4E315C64BFD3}", 71);
classWithNoLoadAsset.m_asset = AZ::Data::Asset<AZ::Data::AssetData>(
testAssetId,
azrtti_typeid<AZ::Data::AssetData>());
classWithNoLoadAsset.m_asset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::NoLoad);
bool gatherResult = AssetBuilderSDK::GatherProductDependencies(*m_serializeContext, &classWithNoLoadAsset, productDependencies, productPathDependencySet);
ASSERT_TRUE(gatherResult);
ASSERT_EQ(productDependencies.size(), 1);
auto behaviorFromFlags = AZ::Data::ProductDependencyInfo::LoadBehaviorFromFlags(productDependencies[0].m_flags);
ASSERT_EQ(behaviorFromFlags, AZ::Data::AssetLoadBehavior::NoLoad);
}
TEST_F(SerializationDependenciesTests, GatherProductDependencies_HasEmptyStringSimpleAsset_NoDependencyEmitted)
{
AZStd::vector<AssetBuilderSDK::ProductDependency> productDependencies;
AssetBuilderSDK::ProductPathDependencySet productPathDependencySet;
ClassWithSimpleAsset classWithSimpleAsset;
classWithSimpleAsset.m_simpleAsset.SetAssetPath("");
bool gatherResult = AssetBuilderSDK::GatherProductDependencies(*m_serializeContext, &classWithSimpleAsset, productDependencies, productPathDependencySet);
ASSERT_TRUE(gatherResult);
ASSERT_EQ(productDependencies.size(), 0);
ASSERT_EQ(productPathDependencySet.size(), 0);
}
}
@@ -0,0 +1,159 @@
/*
* 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 "assetBuilderSDKTest.h"
namespace AssetProcessor
{
#if defined(ENABLE_LEGACY_PLATFORMFLAGS_SUPPORT)
TEST_F(AssetBuilderSDKTest, GetEnabledPlatformsCountUnitTest)
{
AssetBuilderSDK::CreateJobsRequest createJobsRequest;
ASSERT_EQ(createJobsRequest.GetEnabledPlatformsCount(), 0);
createJobsRequest.m_enabledPlatforms = {
{ "pc", {}
}
};
ASSERT_EQ(createJobsRequest.GetEnabledPlatformsCount(), 1);
createJobsRequest.m_enabledPlatforms = {
{ "pc", {}
}, { "es3", {}
}
};
ASSERT_EQ(createJobsRequest.GetEnabledPlatformsCount(), 2);
}
TEST_F(AssetBuilderSDKTest, GetEnabledPlatformAtUnitTest)
{
UnitTestUtils::AssertAbsorber absorb;
AssetBuilderSDK::CreateJobsRequest createJobsRequest;
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(0), AssetBuilderSDK::Platform_NONE);
createJobsRequest.m_enabledPlatforms = {
{ "pc", { }
}
};
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(0), AssetBuilderSDK::Platform_PC);
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(1), AssetBuilderSDK::Platform_NONE);
createJobsRequest.m_enabledPlatforms = {
{ "es3", {}
}
};
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(0), AssetBuilderSDK::Platform_ES3);
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(1), AssetBuilderSDK::Platform_NONE);
createJobsRequest.m_enabledPlatforms = {
{ "pc", {}
}, { "es3", {}
}
};
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(0), AssetBuilderSDK::Platform_PC);
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(1), AssetBuilderSDK::Platform_ES3);
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(2), AssetBuilderSDK::Platform_NONE);
createJobsRequest.m_enabledPlatforms = {
{ "ios", {}
}
};
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(0), AssetBuilderSDK::Platform_IOS);
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(1), AssetBuilderSDK::Platform_NONE);
createJobsRequest.m_enabledPlatforms = {
{ "pc", {}
}, { "es3", {}
}, { "ios", {}
}, { "osx_gl", {}
}
};
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(0), AssetBuilderSDK::Platform_PC);
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(1), AssetBuilderSDK::Platform_ES3);
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(2), AssetBuilderSDK::Platform_IOS);
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(3), AssetBuilderSDK::Platform_OSX);
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(4), AssetBuilderSDK::Platform_NONE);
#if defined(TOOLS_SUPPORT_XENIA)
createJobsRequest.m_enabledPlatforms = {
{ "xenia", {}
}
};
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(0), AssetBuilderSDK::Platform_XENIA);
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(1), AssetBuilderSDK::Platform_NONE);
#endif
createJobsRequest.m_enabledPlatforms = {
{ "pc", {}
}, { "es3", {}
}
};
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(0), AssetBuilderSDK::Platform_PC);
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(1), AssetBuilderSDK::Platform_ES3);
ASSERT_EQ(createJobsRequest.GetEnabledPlatformAt(2), AssetBuilderSDK::Platform_NONE);
// using a deprecated API should have generated warnings.
// but we can't test for it because these warnings are WarningOnce and some other unit test might have already triggered it
}
TEST_F(AssetBuilderSDKTest, IsPlatformEnabledUnitTest)
{
UnitTestUtils::AssertAbsorber absorb;
AssetBuilderSDK::CreateJobsRequest createJobsRequest;
ASSERT_FALSE(createJobsRequest.IsPlatformEnabled(AssetBuilderSDK::Platform_PC));
createJobsRequest.m_enabledPlatforms = {
{ "pc", {}
}
};
ASSERT_TRUE(createJobsRequest.IsPlatformEnabled(AssetBuilderSDK::Platform_PC));
ASSERT_FALSE(createJobsRequest.IsPlatformEnabled(AssetBuilderSDK::Platform_ES3));
createJobsRequest.m_enabledPlatforms = {
{ "pc", {}
}, { "es3", {}
}
};
ASSERT_TRUE(createJobsRequest.IsPlatformEnabled(AssetBuilderSDK::Platform_PC));
ASSERT_TRUE(createJobsRequest.IsPlatformEnabled(AssetBuilderSDK::Platform_ES3));
createJobsRequest.m_enabledPlatforms = {
{ "pc", {}
}, { "es3", {}
}
};
ASSERT_TRUE(createJobsRequest.IsPlatformEnabled(AssetBuilderSDK::Platform_PC));
ASSERT_TRUE(createJobsRequest.IsPlatformEnabled(AssetBuilderSDK::Platform_ES3));
// using a deprecated API should have generated warnings.
// but we can't test for it because these warnings are WarningOnce and some other unit test might have already triggered it
}
TEST_F(AssetBuilderSDKTest, IsPlatformValidUnitTest)
{
AssetBuilderSDK::CreateJobsRequest createJobsRequest;
UnitTestUtils::AssertAbsorber absorb;
ASSERT_TRUE(createJobsRequest.IsPlatformValid(AssetBuilderSDK::Platform_PC));
ASSERT_TRUE(createJobsRequest.IsPlatformValid(AssetBuilderSDK::Platform_ES3));
ASSERT_TRUE(createJobsRequest.IsPlatformValid(AssetBuilderSDK::Platform_IOS));
ASSERT_TRUE(createJobsRequest.IsPlatformValid(AssetBuilderSDK::Platform_OSX));
ASSERT_TRUE(createJobsRequest.IsPlatformValid(AssetBuilderSDK::Platform_XENIA));
ASSERT_TRUE(createJobsRequest.IsPlatformValid(AssetBuilderSDK::Platform_PROVO));
ASSERT_TRUE(createJobsRequest.IsPlatformValid(AssetBuilderSDK::Platform_SALEM));
ASSERT_TRUE(createJobsRequest.IsPlatformValid(AssetBuilderSDK::Platform_JASPER));
//64 is 0x040 which currently is the next valid platform value which is invalid as of now, if we ever add a new platform entry to the Platform enum
//we will have to update this failure unit test
ASSERT_FALSE(createJobsRequest.IsPlatformValid(static_cast<AssetBuilderSDK::Platform>(256)));
// using a deprecated API should have generated warnings.
// but we can't test for it because these warnings are WarningOnce and some other unit test might have already triggered it
}
#endif // defined(ENABLE_LEGACY_PLATFORMFLAGS_SUPPORT)
};
@@ -0,0 +1,35 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/SystemAllocator.h>
namespace AssetProcessor
{
class AssetBuilderSDKTest
: public ::testing::Test
{
protected:
void SetUp() override
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
}
void TearDown() override
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,265 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzCore/std/parallel/atomic.h>
#include <qcoreapplication.h>
#include "native/tests/AssetProcessorTest.h"
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include "native/assetprocessor.h"
#include "native/unittests/UnitTestRunner.h"
#include "native/AssetManager/assetProcessorManager.h"
#include "native/utilities/PlatformConfiguration.h"
#include "native/unittests/MockApplicationManager.h"
#include <AssetManager/FileStateCache.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <QTemporaryDir>
#include <QMetaObject>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include "resourcecompiler/rccontroller.h"
class AssetProcessorManager_Test;
class MockDatabaseLocationListener : public AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Handler
{
public:
MOCK_METHOD1(GetAssetDatabaseLocation, bool(AZStd::string&));
};
class AssetProcessorManagerTest
: public AssetProcessor::AssetProcessorTest
{
public:
AssetProcessorManagerTest();
virtual ~AssetProcessorManagerTest()
{
}
// utility function. Blocks and runs the QT event pump for up to millisecondsMax and will break out as soon as the APM is idle.
bool BlockUntilIdle(int millisecondsMax);
protected:
void SetUp() override;
void TearDown() override;
QTemporaryDir m_tempDir;
AZStd::unique_ptr<AssetProcessorManager_Test> m_assetProcessorManager;
AZStd::unique_ptr<AssetProcessor::MockApplicationManager> m_mockApplicationManager;
AZStd::unique_ptr<AssetProcessor::PlatformConfiguration> m_config;
UnitTestUtils::AssertAbsorber m_assertAbsorber; // absorb asserts/warnings/errors so that the unit test output is not cluttered
QString m_gameName;
QDir m_normalizedCacheRootDir;
AZStd::atomic_bool m_isIdling;
QMetaObject::Connection m_idleConnection;
struct StaticData
{
AZStd::string m_databaseLocation;
::testing::NiceMock<MockDatabaseLocationListener> m_databaseLocationListener;
};
AZStd::unique_ptr<StaticData> m_data;
private:
int m_argc;
char** m_argv;
AZStd::unique_ptr<UnitTestUtils::ScopedDir> m_scopeDir;
AZStd::unique_ptr<QCoreApplication> m_qApp;
};
struct AbsolutePathProductDependencyTest
: public AssetProcessorManagerTest
{
void SetUp() override;
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry SetAndReadAbsolutePathProductDependencyFromRelativePath(
const AZStd::string& relativePath);
AZStd::string BuildScanFolderRelativePath(const AZStd::string& relativePath) const;
AzToolsFramework::AssetDatabase::ProductDatabaseEntry m_productToHaveDependency;
const AssetProcessor::ScanFolderInfo* m_scanFolderInfo = nullptr;
AZStd::string m_testPlatform = "SomePlatform";
};
struct PathDependencyTest
: public AssetProcessorManagerTest
{
void SetUp() override;
void TearDown() override;
using OutputAssetSet = AZStd::vector<AZStd::vector<const char*>>;
struct TestAsset
{
TestAsset() = default;
TestAsset(const char* name) : m_name(name) {}
AZStd::string m_name;
AZStd::vector<AZ::Data::AssetId> m_products;
};
void CaptureJobs(AZStd::vector<AssetProcessor::JobDetails>& jobDetails, const char* sourceFilePath);
bool ProcessAsset(TestAsset& asset, const OutputAssetSet& outputAssets, const AssetBuilderSDK::ProductPathDependencySet& dependencies = {}, const AZStd::string& folderPath = "subfolder1/", const AZStd::string& extension = ".txt");
void RunWildcardTest(bool useCorrectDatabaseSeparator, AssetBuilderSDK::ProductPathDependencyType pathDependencyType, bool buildDependenciesFirst);
AssetProcessor::AssetDatabaseConnection* m_sharedConnection{};
};
struct DuplicateProcessTest
: public PathDependencyTest
{
void SetUp() override;
};
struct MultiplatformPathDependencyTest
: public PathDependencyTest
{
void SetUp() override;
};
struct MockBuilderInfoHandler
: public AssetProcessor::AssetBuilderInfoBus::Handler
{
~MockBuilderInfoHandler();
//! AssetProcessor::AssetBuilderInfoBus Interface
void GetMatchingBuildersInfo(const AZStd::string& assetPath, AssetProcessor::BuilderInfoList& builderInfoList) override;
void GetAllBuildersInfo(AssetProcessor::BuilderInfoList& builderInfoList) override;
void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response);
void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response);
AssetBuilderSDK::AssetBuilderDesc CreateBuilderDesc(const QString& builderName, const QString& builderId, const AZStd::vector<AssetBuilderSDK::AssetBuilderPattern>& builderPatterns);
AssetBuilderSDK::AssetBuilderDesc m_builderDesc;
QString m_jobFingerprint;
QString m_dependencyFilePath;
QString m_jobDependencyFilePath;
int m_createJobsCount = 0;
};
struct ModtimeScanningTest
: public AssetProcessorManagerTest
{
void SetUp() override;
void TearDown() override;
void ProcessAssetJobs();
void SimulateAssetScanner(QSet<AssetProcessor::AssetFileInfo> filePaths);
QSet<AssetProcessor::AssetFileInfo> BuildFileSet();
void ExpectWork(int createJobs, int processJobs);
void ExpectNoWork();
void SetFileContents(QString filePath, QString contents);
struct StaticData
{
QString m_relativePathFromWatchFolder[3];
AZStd::vector<QString> m_absolutePath;
AZStd::vector<AssetProcessor::JobDetails> m_processResults;
AZStd::vector<QString> m_deletedSources;
AZStd::shared_ptr<AssetProcessor::InternalMockBuilder> m_builderTxtBuilder;
MockBuilderInfoHandler m_mockBuilderInfoHandler;
};
AZStd::unique_ptr<StaticData> m_data;
};
struct FingerprintTest
: public AssetProcessorManagerTest
{
void SetUp() override;
void TearDown() override;
void RunFingerprintTest(QString builderFingerprint, QString jobFingerprint, bool expectedResult);
QString m_absolutePath;
MockBuilderInfoHandler m_mockBuilderInfoHandler;
AZStd::vector<AssetProcessor::JobDetails> m_jobResults;
};
struct JobDependencyTest
: public PathDependencyTest
{
void SetUp() override;
void TearDown() override;
struct StaticData
{
MockBuilderInfoHandler m_mockBuilderInfoHandler;
AZ::Uuid m_builderUuid;
};
AZStd::unique_ptr<StaticData> m_data;
};
struct MockMultiBuilderInfoHandler
: public AssetProcessor::AssetBuilderInfoBus::Handler
{
~MockMultiBuilderInfoHandler();
struct AssetBuilderExtraInfo
{
QString m_jobDependencyFilePath;
};
//! AssetProcessor::AssetBuilderInfoBus Interface
void GetMatchingBuildersInfo(const AZStd::string& assetPath, AssetProcessor::BuilderInfoList& builderInfoList) override;
void GetAllBuildersInfo(AssetProcessor::BuilderInfoList& builderInfoList) override;
void CreateJobs(AssetBuilderExtraInfo extraInfo, const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response);
void ProcessJob(AssetBuilderExtraInfo extraInfo, const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response);
void CreateBuilderDesc(const QString& builderName, const QString& builderId, const AZStd::vector<AssetBuilderSDK::AssetBuilderPattern>& builderPatterns, AssetBuilderExtraInfo extraInfo);
AZStd::vector<AssetBuilderSDK::AssetBuilderDesc> m_builderDesc;
AZStd::vector<AssetUtilities::BuilderFilePatternMatcher> m_matcherBuilderPatterns;
AZStd::unordered_map<AZ::Uuid, AssetBuilderSDK::AssetBuilderDesc> m_builderDescMap;
int m_createJobsCount = 0;
};
struct ChainJobDependencyTest
: public PathDependencyTest
{
void SetUp() override;
void TearDown() override;
struct StaticData
{
MockMultiBuilderInfoHandler m_mockBuilderInfoHandler;
AZStd::unique_ptr<AssetProcessor::RCController> m_rcController;
};
static constexpr int ChainLength = 10;
AZStd::unique_ptr<StaticData> m_data;
};
struct DuplicateProductsTest
: public AssetProcessorManagerTest
{
void SetupDuplicateProductsTest(QString& sourceFile, QDir& tempPath, QString& productFile, AZStd::vector<AssetProcessor::JobDetails>& jobDetails, AssetBuilderSDK::ProcessJobResponse& response, bool multipleOutputs, QString extension);
};
struct DeleteTest
: public ModtimeScanningTest
{
void SetUp() override;
};
@@ -0,0 +1,161 @@
/*
* 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 <native/tests/assetscanner/AssetScannerTests.h>
#include <native/AssetManager/assetScanner.h>
namespace AssetProcessor
{
class AssetScanner_Test
: public AssetScanner
{
public:
AssetScanner_Test(PlatformConfiguration* config, QObject* parent = nullptr)
:AssetScanner(config, parent)
{
}
friend class GTEST_TEST_CLASS_NAME_(AssetScannerTest, AssetScannerExcludeFileTest);
friend class GTEST_TEST_CLASS_NAME_(AssetScannerTest, AssetScannerExcludeFolderTest);
};
AssetScannerTest::AssetScannerTest()
:m_argc(0)
,m_argv(0)
{
m_qApp.reset(new QCoreApplication(m_argc,m_argv));
qRegisterMetaType<QSet<QString> >("QSet<QString>");
qRegisterMetaType<AssetProcessor::AssetScanningStatus>("AssetScanningStatus");
qRegisterMetaType<QSet<AssetFileInfo>>("QSet<AssetFileInfo>");
}
bool AssetScannerTest::BlockUntilScanComplete(int millisecondsMax)
{
QElapsedTimer limit;
limit.start();
while ((!m_scanComplete) && (limit.elapsed() < millisecondsMax))
{
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
}
// and then once more, so that any queued events as a result of the above finish.
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
return m_scanComplete;
}
void AssetScannerTest::SetUp()
{
using namespace UnitTestUtils;
AssetProcessorTest::SetUp();
QDir tempPath(m_tempDir.path());
QSet<QString> expectedFiles;
expectedFiles << tempPath.absoluteFilePath("rootfile.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder1/basefile.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder2/basefile.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder2/aaa/basefile.txt");
for (const QString& expect : expectedFiles)
{
EXPECT_TRUE(CreateDummyFile(expect));
}
m_platformConfig.reset(new AssetProcessor::PlatformConfiguration());
AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms;
m_platformConfig.get()->PopulatePlatformsForScanFolder(platforms);
// PATH DisplayName PortKey outputfolder root recurse platforms
m_platformConfig.get()->AddScanFolder(ScanFolderInfo(tempPath.absolutePath(), "", "ap1", "", true, false, platforms));
m_platformConfig.get()->AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder1"), "", "ap2", "", false, true, platforms));
m_platformConfig.get()->AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder2"), "", "ap3", "", false, true, platforms));
m_assetScanner.reset(new AssetScanner_Test(m_platformConfig.get()));
QObject::connect(m_assetScanner.get(), &AssetScanner::FilesFound, [this](QSet<AssetProcessor::AssetFileInfo> fileList)
{
for (AssetProcessor::AssetFileInfo foundFile : fileList)
{
m_files.insert(foundFile.m_filePath);
}
}
);
QObject::connect(m_assetScanner.get(), &AssetScanner::AssetScanningStatusChanged, [this](AssetProcessor::AssetScanningStatus status)
{
if ((status == AssetProcessor::AssetScanningStatus::Completed) || (status == AssetProcessor::AssetScanningStatus::Stopped))
{
m_scanComplete = true;
}
}
);
QObject::connect(m_assetScanner.get(), &AssetScanner::FoldersFound, [this](QSet<AssetProcessor::AssetFileInfo> folderList)
{
for (AssetProcessor::AssetFileInfo foundFolder : folderList)
{
m_folders.insert(foundFolder.m_filePath);
}
}
);
}
void AssetScannerTest::TearDown()
{
m_assetScanner.reset();
m_platformConfig.reset();
QDir tempDir(m_tempDir.path());
tempDir.removeRecursively();
m_qApp.reset();
AssetProcessor::AssetProcessorTest::TearDown();
}
TEST_F(AssetScannerTest, AssetScannerExcludeFileTest)
{
QDir tempDir(m_tempDir.path());
ExcludeAssetRecognizer excludeRecogniser;
excludeRecogniser.m_name = "backup";
// we are excluding all the files in the folder but not the folder itself
excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/subfolder2\\/aaa\\/.*", AssetBuilderSDK::AssetBuilderPattern::Regex);
m_platformConfig.get()->AddExcludeRecognizer(excludeRecogniser);
m_assetScanner.get()->StartScan();
BlockUntilScanComplete(5000);
EXPECT_EQ(m_files.size(), 3);
EXPECT_FALSE(m_files.contains(tempDir.filePath("subfolder2/aaa/basefile.txt")));
EXPECT_EQ(m_folders.size(), 1);
EXPECT_TRUE(m_folders.contains(tempDir.filePath("subfolder2/aaa")));
}
TEST_F(AssetScannerTest, AssetScannerExcludeFolderTest)
{
QDir tempDir(m_tempDir.path());
ExcludeAssetRecognizer excludeRecogniser;
excludeRecogniser.m_name = "backup";
// we are excluding the complete folder here
excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/subfolder2\\/aaa", AssetBuilderSDK::AssetBuilderPattern::Regex);
m_platformConfig.get()->AddExcludeRecognizer(excludeRecogniser);
m_assetScanner.get()->StartScan();
BlockUntilScanComplete(5000);
EXPECT_EQ(m_files.size(), 3);
EXPECT_FALSE(m_files.contains(tempDir.filePath("subfolder2/aaa/basefile.txt")));
EXPECT_EQ(m_folders.size(), 0);
}
}
@@ -0,0 +1,44 @@
/*
* 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 <native/tests/AssetProcessorTest.h>
#include <QTemporaryDir>
#include <QCoreApplication>
#include <native/utilities/PlatformConfiguration.h>
#include <QSet>
#include <QString>
namespace AssetProcessor
{
class AssetScanner_Test;
class AssetScannerTest
: public AssetProcessor::AssetProcessorTest
{
public:
AssetScannerTest();
// Blocks and runs the QT event pump for up to millisecondsMax and will break out as soon as the scan completes.
bool BlockUntilScanComplete(int millisecondsMax);
protected:
void SetUp() override;
void TearDown() override;
int m_argc;
char** m_argv;
QTemporaryDir m_tempDir;
AZStd::unique_ptr<PlatformConfiguration> m_platformConfig;
AZStd::unique_ptr<AssetScanner_Test> m_assetScanner;
QSet<QString> m_files;
QSet<QString> m_folders;
bool m_scanComplete = false;
AZStd::unique_ptr<QCoreApplication> m_qApp;
};
}
@@ -0,0 +1,659 @@
/*
* 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/Settings/SettingsRegistryMergeUtils.h>
#include "native/tests/platformconfiguration/platformconfigurationtests.h"
const char TestAppRoot[] = ":/testdata";
const char EmptyDummyProjectName[] = "EmptyDummyProject";
const char DummyProjectName[] = "DummyProject";
// make the internal calls public for the purposes of the unit test!
class UnitTestPlatformConfiguration : public AssetProcessor::PlatformConfiguration
{
friend class GTEST_TEST_CLASS_NAME_(PlatformConfigurationUnitTests, Test_GemHandling);
friend class GTEST_TEST_CLASS_NAME_(PlatformConfigurationUnitTests, Test_MetaFileTypes);
protected:
};
PlatformConfigurationUnitTests::PlatformConfigurationUnitTests()
: m_argc(0)
, m_argv(0)
{
}
void PlatformConfigurationUnitTests::SetUp()
{
using namespace AssetProcessor;
m_qApp = new QCoreApplication(m_argc, m_argv);
AssetProcessorTest::SetUp();
AssetUtilities::ResetAssetRoot();
}
void PlatformConfigurationUnitTests::TearDown()
{
AssetUtilities::ResetAssetRoot();
delete m_qApp;
AssetProcessor::AssetProcessorTest::TearDown();
}
TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_BadPlatform)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_broken_badplatform";
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
}
TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_NoPlatform)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_broken_noplatform";
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
}
TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_NoScanFolders)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_broken_noscans";
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
}
TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_BrokenRecognizers)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_broken_recognizers";
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
}
TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Regular_Platforms)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_regular";
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
// verify the data.
ASSERT_NE(config.GetPlatformByIdentifier(AzToolsFramework::AssetSystem::GetHostAssetPlatform()), nullptr);
ASSERT_NE(config.GetPlatformByIdentifier("es3"), nullptr);
ASSERT_NE(config.GetPlatformByIdentifier("server"), nullptr);
ASSERT_EQ(config.GetPlatformByIdentifier("xenia"), nullptr);
ASSERT_TRUE(config.GetPlatformByIdentifier("es3")->HasTag("mobile"));
ASSERT_TRUE(config.GetPlatformByIdentifier("es3")->HasTag("renderer"));
ASSERT_TRUE(config.GetPlatformByIdentifier("es3")->HasTag("android"));
ASSERT_TRUE(config.GetPlatformByIdentifier("server")->HasTag("server"));
ASSERT_FALSE(config.GetPlatformByIdentifier("es3")->HasTag("server"));
ASSERT_FALSE(config.GetPlatformByIdentifier("server")->HasTag("renderer"));
}
TEST_F(PlatformConfigurationUnitTests, TestReadScanFolderRoot_FromSettingsRegistry_Succeeds)
{
auto settingsRegistry = AZ::SettingsRegistry::Get();
ASSERT_NE(nullptr, settingsRegistry);
AZ::SettingsRegistryInterface::Specializations apSpecializations;
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_EngineRegistry(*settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, apSpecializations);
struct ScanFolderVisitor
: AZ::SettingsRegistryInterface::Visitor
{
void Visit([[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZ::s64 value)
{
if (valueName == "recursive")
{
m_isRecursive = value != 0;
}
else if (valueName == "order")
{
m_scanOrder = value;
}
}
void Visit([[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZStd::string_view value)
{
if (valueName == "watch")
{
m_watchPath = value;
}
}
AZ::SettingsRegistryInterface::FixedValueString m_watchPath;
bool m_isRecursive{};
int m_scanOrder{};
};
ScanFolderVisitor scanFolderVisitor;
EXPECT_TRUE(settingsRegistry->Visit(scanFolderVisitor, "/Amazon/AssetProcessor/Settings/ScanFolder Root"));
// These test values come from the <dev_root>/Engine/Registry/AssetProcessorPlatformConfig.setreg file
EXPECT_STREQ("@ROOT@", scanFolderVisitor.m_watchPath.c_str());
EXPECT_FALSE(scanFolderVisitor.m_isRecursive);
EXPECT_EQ(10000, scanFolderVisitor.m_scanOrder);
}
// a reusable fixture that sets up one host as a pc with a temp path and such.
class PlatformConfigurationUnitTests_OnePCHostFixture : public PlatformConfigurationUnitTests
{
public:
void SetUp() override
{
PlatformConfigurationUnitTests::SetUp();
m_tempEngineRoot.reset(new QTemporaryDir());
m_tempPath = QDir(m_tempEngineRoot->path());
m_config.reset(new UnitTestPlatformConfiguration());
m_config->EnablePlatform({ "pc",{ "desktop", "host" } }, true);
m_config->PopulatePlatformsForScanFolder(m_platforms);
}
void TearDown() override
{
m_platforms.set_capacity(0);
m_tempEngineRoot.reset();
m_config.reset();
PlatformConfigurationUnitTests::TearDown();
}
AZStd::vector<AssetBuilderSDK::PlatformInfo> m_platforms;
AZStd::unique_ptr<UnitTestPlatformConfiguration> m_config;
AZStd::unique_ptr<QTemporaryDir> m_tempEngineRoot = nullptr; // this actually creates the folder in its constructor, so hold off until setup..
QDir m_tempPath;
};
// ensures that when a file in the root (non recursive) folder is searched for, the root is found.
TEST_F(PlatformConfigurationUnitTests_OnePCHostFixture, GetScanFolderForFile_RootFolderFile_IsFound)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
m_config->AddScanFolder(ScanFolderInfo(m_tempPath.filePath("scanfolder1"), "ScanFolder1", "sf1", "", true, false, m_platforms), true); // a root folder that has watched subfolders, not recursive
m_config->AddScanFolder(ScanFolderInfo(m_tempPath.filePath("scanfolder1/Editor"), "Editor", "sf2", "", false, true, m_platforms), true); // a child folder that exists within that scan folder.
const ScanFolderInfo* info = m_config->GetScanFolderForFile(m_tempPath.filePath("scanfolder1/something.txt"));
ASSERT_TRUE(info);
EXPECT_STREQ(info->ScanPath().toUtf8().constData(), m_tempPath.filePath("scanfolder1").toUtf8().constData());
EXPECT_STREQ(info->GetDisplayName().toUtf8().constData(), "ScanFolder1");
}
// ensures that when a file in a subfolder (recursive) is searched for, the subfolder is found despite it being inside the root, technically.
TEST_F(PlatformConfigurationUnitTests_OnePCHostFixture, GetScanFolderForFile_SubFolderFile_IsFound)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
m_config->AddScanFolder(ScanFolderInfo(m_tempPath.filePath("scanfolder1"), "ScanFolder1", "sf1", "", true, false, m_platforms), true); // a root folder that has watched subfolders, not recursive
m_config->AddScanFolder(ScanFolderInfo(m_tempPath.filePath("scanfolder1/Editor"), "Editor ScanFolder", "sf2", "", false, true, m_platforms), true); // a child folder that exists within that scan folder.
const ScanFolderInfo* info = m_config->GetScanFolderForFile(m_tempPath.filePath("scanfolder1/Editor/something.txt"));
ASSERT_TRUE(info);
EXPECT_STREQ(info->ScanPath().toUtf8().constData(), m_tempPath.filePath("scanfolder1/Editor").toUtf8().constData());
EXPECT_STREQ(info->GetDisplayName().toUtf8().constData(), "Editor ScanFolder");
}
// note that in the case of GetOverridingFile, this SHOULD return the correct case if an override is found
// because its possible to override a file with another file with different case in a different scan folder
// such a situation is supposed to be very rare, so the cost of correcting the case is mitigated.
TEST_F(PlatformConfigurationUnitTests_OnePCHostFixture, GetOverridingFile_Exists_ReturnsCorrectCase)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
// create two scan folders, since its order dependent, the ScanFolder1 is the "winner" in tie breakers (when they both contain same file relpath)
QString scanfolder1Path = m_tempPath.filePath("scanfolder1");
QString scanfolder2Path = m_tempPath.filePath("scanfolder2");
QString caseSensitiveDummyFileName = m_tempPath.absoluteFilePath("scanfolder1/TestCase.tXt");
QString differentCaseDummyFileName = m_tempPath.absoluteFilePath("scanfolder2/testcase.txt");
UnitTestUtils::CreateDummyFile(caseSensitiveDummyFileName, QString("testcase1\n"));
UnitTestUtils::CreateDummyFile(differentCaseDummyFileName, QString("testcase2\n"));
m_config->AddScanFolder(ScanFolderInfo(scanfolder1Path, "ScanFolder1", "sf1", "", false, true, m_platforms), true);
m_config->AddScanFolder(ScanFolderInfo(scanfolder2Path, "ScanFolder2", "sf2", "", false, true, m_platforms), true);
// Perform the test by asking it whether anyone overrides "testcase" (lowercase) in scanfolder 2.
QString overrider = m_config->GetOverridingFile("testcase.txt", scanfolder2Path);
ASSERT_FALSE(overrider.isEmpty());
// the result should be the real actual case of the file in scanfolder 1:
EXPECT_STREQ(overrider.toUtf8().constData(), caseSensitiveDummyFileName.toUtf8().constData());
}
TEST_F(PlatformConfigurationUnitTests_OnePCHostFixture, GetOverridingFile_ExistsButNotOverridden_ReturnsEmpty)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
// create two scan folders, since its order dependent, the ScanFolder1 is the "winner" in tie breakers.
QString scanfolder1Path = m_tempPath.filePath("scanfolder1");
QString scanfolder2Path = m_tempPath.filePath("scanfolder2");
QString caseSensitiveDummyFileName = m_tempPath.absoluteFilePath("scanfolder1/TestCase.tXt");
QString differentCaseDummyFileName = m_tempPath.absoluteFilePath("scanfolder2/testcase.txt");
UnitTestUtils::CreateDummyFile(caseSensitiveDummyFileName, QString("testcase1\n"));
UnitTestUtils::CreateDummyFile(differentCaseDummyFileName, QString("testcase2\n"));
m_config->AddScanFolder(ScanFolderInfo(scanfolder1Path, "ScanFolder1", "sf1", "", false, true, m_platforms), true);
m_config->AddScanFolder(ScanFolderInfo(scanfolder2Path, "ScanFolder2", "sf2", "", false, true, m_platforms), true);
// Perform the test by asking it whether the existing real winning file is being overridden by anyone.
QString overrider = m_config->GetOverridingFile("TestCase.tXt", scanfolder1Path);
// note that this should return the emptystring, because there is nothing that OVERRIDES it (ie, its already the winner).
EXPECT_TRUE(overrider.isEmpty());
}
TEST_F(PlatformConfigurationUnitTests_OnePCHostFixture, GetOverridingFile_DoesNotExist_ReturnsEmptyString)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
// create two scan folders, since its order dependent, the ScanFolder1 is the "winner" in tie breakers.
QString scanfolder1Path = m_tempPath.filePath("scanfolder1");
QString scanfolder2Path = m_tempPath.filePath("scanfolder2");
m_config->AddScanFolder(ScanFolderInfo(scanfolder1Path, "ScanFolder1", "sf1", "", false, true, m_platforms), true);
m_config->AddScanFolder(ScanFolderInfo(scanfolder2Path, "ScanFolder2", "sf2", "", false, true, m_platforms), true);
// Perform the test by asking it whether anyone overrides "testcase" (lowercase) in scanfolder 2.
QString overrider = m_config->GetOverridingFile("doesntExist.txt", scanfolder2Path);
EXPECT_TRUE(overrider.isEmpty());
}
TEST_F(PlatformConfigurationUnitTests_OnePCHostFixture, FindFirstMatchingFile_DoesNotExist_ReturnsEmptyString)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
// create two scan folders, since its order dependent, the ScanFolder1 is the "winner" in tie breakers.
QString scanfolder1Path = m_tempPath.filePath("scanfolder1");
QString scanfolder2Path = m_tempPath.filePath("scanfolder2");
m_config->AddScanFolder(ScanFolderInfo(scanfolder1Path, "ScanFolder1", "sf1", "", false, true, m_platforms), true);
m_config->AddScanFolder(ScanFolderInfo(scanfolder2Path, "ScanFolder2", "sf2", "", false, true, m_platforms), true);
// Perform the test by asking it whether anyone overrides "testcase" (lowercase) in scanfolder 2.
QString foundFile = m_config->FindFirstMatchingFile("doesntExist.txt");
EXPECT_TRUE(foundFile.isEmpty());
}
// note that we do not guarantee that FindFirstMatchingFile always returns the correct case, as it is a super hot path
// function, and the only time case could be incorrect is in the situation where a file with different case overrides
// an underlying file, ie,
// Engine/EngineAssets/Textures/StartScreen.tif
// MyGame/EngineAssets/textures/startscreen.tif <-- would override the above because game has higher / more important priority.
// ensures that exact matches take priority over subfolder matches
TEST_F(PlatformConfigurationUnitTests_OnePCHostFixture, GetScanFolderForFile_SubFolder_ExactMatch_IsFound)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
m_config->AddScanFolder(ScanFolderInfo(m_tempPath.filePath("scanfolder1"), "ScanFolder1", "sf1", "", true, false, m_platforms), true); // a root folder that has watched subfolders, not recursive
m_config->AddScanFolder(ScanFolderInfo(m_tempPath.filePath("scanfolder1/Editor"), "Editor ScanFolder", "sf2", "", false, true, m_platforms), true); // a child folder that exists within that scan folder.
const ScanFolderInfo* info = m_config->GetScanFolderForFile(m_tempPath.filePath("scanfolder1/Editor"));
ASSERT_TRUE(info);
EXPECT_STREQ(info->ScanPath().toUtf8().constData(), m_tempPath.filePath("scanfolder1/Editor").toUtf8().constData());
EXPECT_STREQ(info->GetDisplayName().toUtf8().constData(), "Editor ScanFolder");
}
TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularScanfolder)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_regular";
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
ASSERT_EQ(config.GetScanFolderCount(), 3); // the two, and then the one that has the same data as prior but different identifier.
QString scanName = AssetUtilities::ComputeGameName() + " Scan Folder";
ASSERT_EQ(config.GetScanFolderAt(0).GetDisplayName(), scanName);
ASSERT_EQ(config.GetScanFolderAt(0).GetOutputPrefix(), QString());
ASSERT_EQ(config.GetScanFolderAt(0).RecurseSubFolders(), true);
ASSERT_EQ(config.GetScanFolderAt(0).GetOrder(), 0);
// its important that this does NOT change and this makes sure the old way of doing it (case-sensitive name) persists
ASSERT_EQ(config.GetScanFolderAt(0).GetPortableKey(), QString("from-ini-file-Game"));
ASSERT_EQ(config.GetScanFolderAt(1).GetDisplayName(), QString("FeatureTests"));
ASSERT_EQ(config.GetScanFolderAt(1).GetOutputPrefix(), QString("featuretestsoutputfolder")); // to prove its not related to display name
ASSERT_EQ(config.GetScanFolderAt(1).RecurseSubFolders(), false);
ASSERT_EQ(config.GetScanFolderAt(1).GetOrder(), 5000);
// this proves that the featuretests name is used instead of the output prefix
ASSERT_EQ(config.GetScanFolderAt(1).GetPortableKey(), QString("from-ini-file-FeatureTests"));
ASSERT_EQ(config.GetScanFolderAt(2).GetDisplayName(), QString("FeatureTests2"));
ASSERT_EQ(config.GetScanFolderAt(2).GetOutputPrefix(), QString("featuretestsoutputfolder")); // to prove its not related to display name
ASSERT_EQ(config.GetScanFolderAt(2).RecurseSubFolders(), false);
ASSERT_EQ(config.GetScanFolderAt(2).GetOrder(), 6000);
// this proves that the featuretests name is used instead of the output prefix
ASSERT_EQ(config.GetScanFolderAt(2).GetPortableKey(), QString("from-ini-file-FeatureTests2"));
}
TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularScanfolderPlatformSpecific)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_regular_platform_scanfolder";
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
ASSERT_EQ(config.GetScanFolderCount(), 5);
ASSERT_EQ(config.GetScanFolderAt(0).GetDisplayName(), QString("gameoutput"));
AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms = config.GetScanFolderAt(0).GetPlatforms();
ASSERT_EQ(platforms.size(), 4);
ASSERT_TRUE(AZStd::find(platforms.begin(), platforms.end(), AssetBuilderSDK::PlatformInfo(AzToolsFramework::AssetSystem::GetHostAssetPlatform(), AZStd::unordered_set<AZStd::string>{})) != platforms.end());
ASSERT_TRUE(AZStd::find(platforms.begin(), platforms.end(), AssetBuilderSDK::PlatformInfo("es3", AZStd::unordered_set<AZStd::string>{})) != platforms.end());
ASSERT_TRUE(AZStd::find(platforms.begin(), platforms.end(), AssetBuilderSDK::PlatformInfo("ios", AZStd::unordered_set<AZStd::string>{})) != platforms.end());
ASSERT_TRUE(AZStd::find(platforms.begin(), platforms.end(), AssetBuilderSDK::PlatformInfo("server", AZStd::unordered_set<AZStd::string>{})) != platforms.end());
ASSERT_EQ(config.GetScanFolderAt(1).GetDisplayName(), QString("editoroutput"));
platforms = config.GetScanFolderAt(1).GetPlatforms();
ASSERT_EQ(platforms.size(), 2);
ASSERT_TRUE(AZStd::find(platforms.begin(), platforms.end(), AssetBuilderSDK::PlatformInfo(AzToolsFramework::AssetSystem::GetHostAssetPlatform(), AZStd::unordered_set<AZStd::string>{})) != platforms.end());
ASSERT_TRUE(AZStd::find(platforms.begin(), platforms.end(), AssetBuilderSDK::PlatformInfo("es3", AZStd::unordered_set<AZStd::string>{})) != platforms.end());
ASSERT_EQ(config.GetScanFolderAt(2).GetDisplayName(), QString("folder1output"));
platforms = config.GetScanFolderAt(2).GetPlatforms();
ASSERT_EQ(platforms.size(), 1);
ASSERT_TRUE(AZStd::find(platforms.begin(), platforms.end(), AssetBuilderSDK::PlatformInfo("es3", AZStd::unordered_set<AZStd::string>{})) != platforms.end());
ASSERT_EQ(config.GetScanFolderAt(3).GetDisplayName(), QString("folder2output"));
platforms = config.GetScanFolderAt(3).GetPlatforms();
ASSERT_EQ(platforms.size(), 3);
ASSERT_TRUE(AZStd::find(platforms.begin(), platforms.end(), AssetBuilderSDK::PlatformInfo(AzToolsFramework::AssetSystem::GetHostAssetPlatform(), AZStd::unordered_set<AZStd::string>{})) != platforms.end());
ASSERT_TRUE(AZStd::find(platforms.begin(), platforms.end(), AssetBuilderSDK::PlatformInfo("ios", AZStd::unordered_set<AZStd::string>{})) != platforms.end());
ASSERT_TRUE(AZStd::find(platforms.begin(), platforms.end(), AssetBuilderSDK::PlatformInfo("server", AZStd::unordered_set<AZStd::string>{})) != platforms.end());
ASSERT_EQ(config.GetScanFolderAt(4).GetDisplayName(), QString("folder3output"));
platforms = config.GetScanFolderAt(4).GetPlatforms();
ASSERT_EQ(platforms.size(), 0);
}
TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularExcludes)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_regular";
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
ASSERT_TRUE(config.IsFileExcluded("blahblah/$tmp_01.test"));
ASSERT_FALSE(config.IsFileExcluded("blahblah/tmp_01.test"));
ASSERT_TRUE(config.IsFileExcluded("blahblah/Levels/blahblah_hold/whatever.test"));
ASSERT_FALSE(config.IsFileExcluded("blahblah/Levels/blahblahhold/whatever.test"));
}
TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Recognizers)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
#if defined(AZ_PLATFORM_WINDOWS)
const char* platformWhichIsNotCurrentPlatform = "osx_gl";
#else
const char* platformWhichIsNotCurrentPlatform = "pc";
#endif
const char* configRoot = ":/testdata/config_regular";
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
const AssetProcessor::RecognizerContainer& recogs = config.GetAssetRecognizerContainer();
ASSERT_EQ(recogs.size(), 6);
ASSERT_TRUE(recogs.contains("i_caf"));
ASSERT_EQ(recogs["i_caf"].m_patternMatcher.GetBuilderPattern().m_pattern, "*.i_caf");
ASSERT_EQ(recogs["i_caf"].m_patternMatcher.GetBuilderPattern().m_type, AssetBuilderSDK::AssetBuilderPattern::Wildcard);
ASSERT_EQ(recogs["i_caf"].m_platformSpecs.size(), 2);
ASSERT_TRUE(recogs["i_caf"].m_platformSpecs.contains("es3"));
ASSERT_TRUE(recogs["i_caf"].m_platformSpecs.contains(AzToolsFramework::AssetSystem::GetHostAssetPlatform()));
ASSERT_FALSE(recogs["i_caf"].m_platformSpecs.contains("server")); // server has been set to skip.
ASSERT_EQ(recogs["i_caf"].m_platformSpecs["es3"].m_extraRCParams, "mobile");
ASSERT_EQ(recogs["i_caf"].m_platformSpecs[AzToolsFramework::AssetSystem::GetHostAssetPlatform()].m_extraRCParams, "defaultparams");
ASSERT_TRUE(recogs.contains("caf"));
ASSERT_TRUE(recogs["caf"].m_platformSpecs.contains("es3"));
ASSERT_TRUE(recogs["caf"].m_platformSpecs.contains("server"));
ASSERT_TRUE(recogs["caf"].m_platformSpecs.contains(AzToolsFramework::AssetSystem::GetHostAssetPlatform()));
ASSERT_EQ(recogs["caf"].m_platformSpecs.size(), 3);
ASSERT_EQ(recogs["caf"].m_platformSpecs["es3"].m_extraRCParams, "rendererparams");
ASSERT_EQ(recogs["caf"].m_platformSpecs[AzToolsFramework::AssetSystem::GetHostAssetPlatform()].m_extraRCParams, "rendererparams");
ASSERT_EQ(recogs["caf"].m_platformSpecs["server"].m_extraRCParams, "copy");
ASSERT_TRUE(recogs.contains("mov"));
ASSERT_TRUE(recogs["mov"].m_platformSpecs.contains("es3"));
ASSERT_TRUE(recogs["mov"].m_platformSpecs.contains("server"));
ASSERT_TRUE(recogs["mov"].m_platformSpecs.contains(AzToolsFramework::AssetSystem::GetHostAssetPlatform()));
ASSERT_EQ(recogs["mov"].m_platformSpecs.size(), 3);
ASSERT_EQ(recogs["mov"].m_platformSpecs["es3"].m_extraRCParams, "platformspecificoverride");
ASSERT_EQ(recogs["mov"].m_platformSpecs[AzToolsFramework::AssetSystem::GetHostAssetPlatform()].m_extraRCParams, "rendererparams");
ASSERT_EQ(recogs["mov"].m_platformSpecs["server"].m_extraRCParams, "copy");
// the "rend" test makes sure that even if you dont specify 'params' its still there by default for all enabled platforms.
// (but platforms can override it)
ASSERT_TRUE(recogs.contains("rend"));
ASSERT_TRUE(recogs["rend"].m_platformSpecs.contains(AzToolsFramework::AssetSystem::GetHostAssetPlatform()));
ASSERT_TRUE(recogs["rend"].m_platformSpecs.contains("es3"));
ASSERT_TRUE(recogs["rend"].m_platformSpecs.contains("server"));
ASSERT_FALSE(recogs["rend"].m_platformSpecs.contains(platformWhichIsNotCurrentPlatform)); // this is not an enabled platform and should not be there.
ASSERT_EQ(recogs["rend"].m_platformSpecs.size(), 3);
ASSERT_EQ(recogs["rend"].m_platformSpecs[AzToolsFramework::AssetSystem::GetHostAssetPlatform()].m_extraRCParams, "rendererparams");
ASSERT_EQ(recogs["rend"].m_platformSpecs["es3"].m_extraRCParams, "rendererparams");
ASSERT_EQ(recogs["rend"].m_platformSpecs["server"].m_extraRCParams, ""); // default if not specified is empty string
ASSERT_TRUE(recogs.contains("alldefault"));
ASSERT_TRUE(recogs["alldefault"].m_platformSpecs.contains(AzToolsFramework::AssetSystem::GetHostAssetPlatform()));
ASSERT_TRUE(recogs["alldefault"].m_platformSpecs.contains("es3"));
ASSERT_TRUE(recogs["alldefault"].m_platformSpecs.contains("server"));
ASSERT_FALSE(recogs["alldefault"].m_platformSpecs.contains(platformWhichIsNotCurrentPlatform)); // this is not an enabled platform and should not be there.
ASSERT_EQ(recogs["alldefault"].m_platformSpecs.size(), 3);
ASSERT_EQ(recogs["alldefault"].m_platformSpecs[AzToolsFramework::AssetSystem::GetHostAssetPlatform()].m_extraRCParams, "");
ASSERT_EQ(recogs["alldefault"].m_platformSpecs["es3"].m_extraRCParams, "");
ASSERT_EQ(recogs["alldefault"].m_platformSpecs["server"].m_extraRCParams, "");
ASSERT_TRUE(recogs.contains("skipallbutone"));
ASSERT_FALSE(recogs["skipallbutone"].m_platformSpecs.contains(AzToolsFramework::AssetSystem::GetHostAssetPlatform()));
ASSERT_FALSE(recogs["skipallbutone"].m_platformSpecs.contains("es3"));
ASSERT_TRUE(recogs["skipallbutone"].m_platformSpecs.contains("server")); // server is only one enabled (set to copy)
ASSERT_EQ(recogs["skipallbutone"].m_platformSpecs.size(), 1);
ASSERT_EQ(recogs["skipallbutone"].m_platformSpecs["server"].m_extraRCParams, "copy");
}
TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Overrides)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_regular";
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, DummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
const AssetProcessor::RecognizerContainer& recogs = config.GetAssetRecognizerContainer();
// note that the override config DISABLES the server platform - and this in turn disables the "server only" compile rule called skipallbutone
// verify the data.
ASSERT_NE(config.GetPlatformByIdentifier(AzToolsFramework::AssetSystem::GetHostAssetPlatform()), nullptr);
ASSERT_NE(config.GetPlatformByIdentifier("es3"), nullptr);
ASSERT_NE(config.GetPlatformByIdentifier("provo"), nullptr);
// this override swaps server with provo in that it turns ON provo, turns off server
ASSERT_EQ(config.GetPlatformByIdentifier("xenia"), nullptr);
ASSERT_EQ(config.GetPlatformByIdentifier("server"), nullptr); // this should be off due to overrides
// there is a rule which only output on server, so that rule should be omitted
ASSERT_FALSE(recogs.contains("skipallbutone")); // this is the rule that had only a server.
// this exists in config_regular.ini but is removed by config_overrides.ini
ASSERT_FALSE(recogs.contains("mov"));
ASSERT_EQ(recogs.size(), 4); // so there's 4 instead of 6 because of the above omissions
ASSERT_TRUE(recogs.contains("i_caf"));
ASSERT_EQ(recogs["i_caf"].m_patternMatcher.GetBuilderPattern().m_pattern, "*.i_caf");
ASSERT_EQ(recogs["i_caf"].m_patternMatcher.GetBuilderPattern().m_type, AssetBuilderSDK::AssetBuilderPattern::Wildcard);
ASSERT_EQ(recogs["i_caf"].m_platformSpecs.size(), 3);
ASSERT_TRUE(recogs["i_caf"].m_platformSpecs.contains("es3"));
ASSERT_TRUE(recogs["i_caf"].m_platformSpecs.contains("provo"));
ASSERT_TRUE(recogs["i_caf"].m_platformSpecs.contains(AzToolsFramework::AssetSystem::GetHostAssetPlatform()));
ASSERT_FALSE(recogs["i_caf"].m_platformSpecs.contains("server")); // server has been set to skip.
ASSERT_EQ(recogs["i_caf"].m_platformSpecs["es3"].m_extraRCParams, "mobile");
ASSERT_EQ(recogs["i_caf"].m_platformSpecs[AzToolsFramework::AssetSystem::GetHostAssetPlatform()].m_extraRCParams, "defaultparams");
ASSERT_EQ(recogs["i_caf"].m_platformSpecs["provo"].m_extraRCParams, "copy");
}
TEST_F(PlatformConfigurationUnitTests, Test_GemHandling)
{
UnitTestPlatformConfiguration config;
QTemporaryDir tempEngineRoot;
QDir tempPath(tempEngineRoot.path());
AssetUtilities::ResetAssetRoot();
AssetUtilities::ComputeGameName("SamplesProject", true);
QDir computedEngineRoot;
ASSERT_TRUE(AssetUtilities::ComputeAssetRoot(computedEngineRoot, &tempPath));
ASSERT_TRUE(!computedEngineRoot.absolutePath().isEmpty());
ASSERT_TRUE(tempPath.absolutePath() == computedEngineRoot.absolutePath());
// create ONE of the two files - they are optional, but the paths to them should always be checked and generated.
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("Gems/LyShine/AssetProcessorGemConfig.ini"), ";nothing to see here"));
// note that it is expected that the gems system gives us absolute paths.
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> fakeGems;
fakeGems.push_back({ "LyShine", "Gems/LyShine", tempPath.absoluteFilePath("Gems/LyShine").toUtf8().constData(), "0fefab3f13364722b2eab3b96ce2bf20", true, false });// true = pretend this is a game gem.
fakeGems.push_back({ "LmbrCentral", "Gems/LmbrCentral/v2", tempPath.absoluteFilePath("Gems/LmbrCentral/v2").toUtf8().constData(), "ff06785f7145416b9d46fde39098cb0c", false, false });
// reading gems via the Gems System is already to be tested in the actual Gems API tests.
// to avoid trying to load those DLLs we avoid calling the actual ReadGems function
config.AddGemScanFolders(fakeGems);
QString expectedScanFolder = tempPath.absoluteFilePath("Gems/LyShine/Assets");
AssetUtilities::ResetAssetRoot();
ASSERT_EQ(2, config.GetScanFolderCount());
EXPECT_FALSE(config.GetScanFolderAt(0).IsRoot());
EXPECT_TRUE(config.GetScanFolderAt(0).GetOutputPrefix().isEmpty());
EXPECT_TRUE(config.GetScanFolderAt(0).RecurseSubFolders());
// the first one is a game gem, so its order should be above 1 but below 100.
EXPECT_GE(config.GetScanFolderAt(0).GetOrder(), 1);
EXPECT_LE(config.GetScanFolderAt(0).GetOrder(), 100);
EXPECT_EQ(0, config.GetScanFolderAt(0).ScanPath().compare(expectedScanFolder, Qt::CaseInsensitive));
// for each gem, there are currently 1 scan folder, the gem assets folder, with no output prefix
expectedScanFolder = tempPath.absoluteFilePath("Gems/LmbrCentral/v2/Assets");
EXPECT_FALSE(config.GetScanFolderAt(1).IsRoot() );
EXPECT_TRUE(config.GetScanFolderAt(1).GetOutputPrefix().isEmpty());
EXPECT_TRUE(config.GetScanFolderAt(1).RecurseSubFolders());
EXPECT_GT(config.GetScanFolderAt(1).GetOrder(), config.GetScanFolderAt(0).GetOrder());
EXPECT_EQ(0, config.GetScanFolderAt(1).ScanPath().compare(expectedScanFolder, Qt::CaseInsensitive));
}
TEST_F(PlatformConfigurationUnitTests, Test_MetaFileTypes)
{
UnitTestPlatformConfiguration config;
config.AddMetaDataType("xxxx", "");
config.AddMetaDataType("yyyy", "zzzz");
ASSERT_TRUE(config.MetaDataFileTypesCount() == 2);
ASSERT_TRUE(QString::compare(config.GetMetaDataFileTypeAt(1).first, "yyyy", Qt::CaseInsensitive) == 0);
ASSERT_TRUE(QString::compare(config.GetMetaDataFileTypeAt(1).second, "zzzz", Qt::CaseInsensitive) == 0);
}
TEST_F(PlatformConfigurationUnitTests, ReadCheckSever_FromConfig_Valid)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_regular";
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
const AssetProcessor::RecognizerContainer& recogs = config.GetAssetRecognizerContainer();
// verify that check server flag is set to true for i_caf
ASSERT_TRUE(recogs.contains("i_caf"));
ASSERT_TRUE(recogs["i_caf"].m_checkServer);
}
TEST_F(PlatformConfigurationUnitTests, PlatformConfigFile_IsPresent_Found)
{
UnitTestPlatformConfiguration config;
QTemporaryDir tempEngineRoot;
QDir tempPath(tempEngineRoot.path());
AssetUtilities::ResetAssetRoot();
AssetUtilities::ComputeGameName("SamplesProject", true);
QDir computedEngineRoot;
ASSERT_TRUE(AssetUtilities::ComputeAssetRoot(computedEngineRoot, &tempPath));
ASSERT_TRUE(AssetUtilities::ComputeEngineRoot(computedEngineRoot, &tempPath));
ASSERT_TRUE(!computedEngineRoot.absolutePath().isEmpty());
ASSERT_TRUE(tempPath.absolutePath() == computedEngineRoot.absolutePath());
// create ONE of the two files - they are optional, but the paths to them should always be checked and generated.
QString platformConfigPath{ AssetProcessor::AssetConfigPlatformDir };
platformConfigPath.append("TestPlatform/");
platformConfigPath.append(AssetProcessor::AssetProcessorPlatformConfigFileName);
QStringList platformConfigList;
ASSERT_FALSE(config.AddPlatformConfigFilePaths(platformConfigList));
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath(platformConfigPath), ";nothing to see here"));
ASSERT_TRUE(config.AddPlatformConfigFilePaths(platformConfigList));
ASSERT_EQ(platformConfigList.size(), 1);
}
@@ -0,0 +1,42 @@
/*
* 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 <AzTest/AzTest.h>
#include <QCoreApplication>
#include "native/tests/AssetProcessorTest.h"
#include "native/unittests/UnitTestRunner.h"
#include "native/utilities/PlatformConfiguration.h"
#include <AssetManager/FileStateCache.h>
class PlatformConfigurationUnitTests
: public AssetProcessor::AssetProcessorTest
{
public:
PlatformConfigurationUnitTests();
virtual ~PlatformConfigurationUnitTests()
{
}
protected:
void SetUp() override;
void TearDown() override;
UnitTestUtils::AssertAbsorber m_absorber;
AssetProcessor::FileStatePassthrough m_fileStateCache;
private:
int m_argc;
char** m_argv;
QCoreApplication* m_qApp;
};
@@ -0,0 +1,888 @@
/*
* 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 "RCBuilderTest.h"
TEST_F(RCBuilderTest, CreateBuilderDesc_CreateBuilder_Valid)
{
AssetBuilderSDK::AssetBuilderPattern pattern;
pattern.m_pattern = "*.foo";
AZStd::vector<AssetBuilderSDK::AssetBuilderPattern> builderPatterns;
builderPatterns.push_back(pattern);
TestInternalRecognizerBasedBuilder test(new MockRCCompiler());
AssetBuilderSDK::AssetBuilderDesc result = test.CreateBuilderDesc(this->GetBuilderID(), builderPatterns);
ASSERT_EQ(this->GetBuilderName(), result.m_name);
ASSERT_EQ(this->GetBuilderUUID(), result.m_busId);
ASSERT_EQ(false, result.IsExternalBuilder());
ASSERT_TRUE(result.m_patterns.size() == 1);
ASSERT_EQ(result.m_patterns[0].m_pattern, pattern.m_pattern);
}
TEST_F(RCBuilderTest, Shutdown_NormalShutdown_Requested)
{
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
test.ShutDown();
ASSERT_EQ(mockRC->m_request_quit, 1);
}
TEST_F(RCBuilderTest, Initialize_StandardInitialization_Fail)
{
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
mockRC->SetResultInitialize(false);
bool initialization_result = test.Initialize(configuration);
ASSERT_FALSE(initialization_result);
}
TEST_F(RCBuilderTest, Initialize_StandardInitializationWithDuplicateAndInvalidRecognizers_Valid)
{
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
// 3 Asset recognizers, 1 duplicate & 1 without platform should result in only 1 InternalAssetRecognizer
// Good spec
AssetRecognizer good;
good.m_name = "Good";
good.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.foo", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard);
AssetPlatformSpec good_spec;
good_spec.m_extraRCParams = "/i";
good.m_platformSpecs["pc"] = good_spec;
// No Platform spec
AssetRecognizer no_platform;
no_platform.m_name = "No Platform";
no_platform.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.ccc", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard);
// Duplicate
AssetRecognizer duplicate(good.m_name, good.m_testLockSource, good.m_priority, good.m_isCritical, good.m_supportsCreateJobs, good.m_patternMatcher, good.m_version, good.m_productAssetType, good.m_outputProductDependencies);
duplicate.m_platformSpecs["pc"] = good_spec;
configuration.m_recognizerContainer["good"] = good;
configuration.m_recognizerContainer["no_platform"] = no_platform;
configuration.m_recognizerContainer["duplicate"] = duplicate;
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
ASSERT_EQ(mockRC->m_initialize, 1);
AZStd::vector<AssetBuilderSDK::PlatformInfo> platformInfos;
AZStd::unordered_set<AZStd::string> tags;
tags.insert("tools");
tags.insert("desktop");
platformInfos.emplace_back(AssetBuilderSDK::PlatformInfo("pc", tags));
InternalRecognizerPointerContainer good_recognizers;
bool good_recognizers_found = test.GetMatchingRecognizers(platformInfos, "test.foo", good_recognizers);
ASSERT_TRUE(good_recognizers_found); // Should find at least 1
ASSERT_EQ(good_recognizers.size(), 1); // 1, not 2 since the duplicates should be removed
ASSERT_EQ(good_recognizers.at(0)->m_name, good.m_name); // Match the same recognizer
InternalRecognizerPointerContainer bad_recognizers;
bool no_recognizers_found = !test.GetMatchingRecognizers(platformInfos, "test.ccc", good_recognizers);
ASSERT_TRUE(no_recognizers_found);
ASSERT_EQ(bad_recognizers.size(), 0); // 1, not 2 since the duplicates should be removed
ASSERT_EQ(m_errorAbsorber->m_numWarningsAbsorbed, 1); // this should be the "duplicate builder" warning.
ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
ASSERT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
}
TEST_F(RCBuilderTest, CreateJobs_CreateSingleJobStandard_Valid)
{
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
AssetRecognizer good;
good.m_name = "Good";
good.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.foo", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard);
AssetPlatformSpec good_spec;
good_spec.m_extraRCParams = "/i";
good.m_platformSpecs["pc"] = good_spec;
configuration.m_recognizerContainer["good"] = good;
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
AssetBuilderSDK::CreateJobsRequest request;
AssetBuilderSDK::CreateJobsResponse response;
request.m_watchFolder = "c:\temp";
request.m_sourceFile = "test.foo";
request.m_enabledPlatforms = { AssetBuilderSDK::PlatformInfo("pc", { "desktop", "renderer" }) };
AssetProcessor::BUILDER_ID_RC.GetUuid(request.m_builderid);
test.CreateJobs(request, response);
ASSERT_EQ(response.m_result, AssetBuilderSDK::CreateJobsResultCode::Success);
ASSERT_EQ(response.m_createJobOutputs.size(), 1);
AssetBuilderSDK::JobDescriptor descriptor = response.m_createJobOutputs.at(0);
ASSERT_EQ(descriptor.GetPlatformIdentifier(), "pc");
ASSERT_FALSE(descriptor.m_critical);
}
TEST_F(RCBuilderTest, CreateJobs_CreateMultiplesJobStandard_Valid)
{
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
AssetRecognizer standard_AR_RC;
const AZStd::string job_key_rc = "RCjob";
{
standard_AR_RC.m_name = "RCjob";
standard_AR_RC.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.foo", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard);
AssetPlatformSpec good_rc_spec;
good_rc_spec.m_extraRCParams = "/i";
standard_AR_RC.m_platformSpecs["pc"] = good_rc_spec;
}
configuration.m_recognizerContainer["rc_foo"] = standard_AR_RC;
AssetRecognizer standard_AR_Copy;
const AZStd::string job_key_copy = "Copyjob";
{
standard_AR_Copy.m_name = QString(job_key_copy.c_str());
standard_AR_Copy.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.foo", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard);
AssetPlatformSpec good_copy_spec;
good_copy_spec.m_extraRCParams = "copy";
standard_AR_Copy.m_platformSpecs["pc"] = good_copy_spec;
}
configuration.m_recognizerContainer["copy_foo"] = standard_AR_Copy;
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
// Request is for the copy builder
{
AssetBuilderSDK::CreateJobsRequest request_copy;
AssetBuilderSDK::CreateJobsResponse response_copy;
request_copy.m_watchFolder = "c:\temp";
request_copy.m_sourceFile = "test.foo";
request_copy.m_enabledPlatforms = { AssetBuilderSDK::PlatformInfo("pc",{ "desktop", "renderer" }) };
AssetProcessor::BUILDER_ID_COPY.GetUuid(request_copy.m_builderid);
test.CreateJobs(request_copy, response_copy);
ASSERT_EQ(response_copy.m_result, AssetBuilderSDK::CreateJobsResultCode::Success);
ASSERT_EQ(response_copy.m_createJobOutputs.size(), 1);
AssetBuilderSDK::JobDescriptor descriptor = response_copy.m_createJobOutputs.at(0);
ASSERT_EQ(descriptor.GetPlatformIdentifier(), "pc");
ASSERT_EQ(descriptor.m_jobKey.compare(job_key_copy), 0);
ASSERT_TRUE(descriptor.m_critical);
}
// Request is for the rc builder
{
AssetBuilderSDK::CreateJobsRequest request_rc;
AssetBuilderSDK::CreateJobsResponse response_rc;
request_rc.m_watchFolder = "c:\temp";
request_rc.m_sourceFile = "test.foo";
request_rc.m_enabledPlatforms = { AssetBuilderSDK::PlatformInfo("pc",{ "desktop", "renderer" }) };
AssetProcessor::BUILDER_ID_RC.GetUuid(request_rc.m_builderid);
test.CreateJobs(request_rc, response_rc);
ASSERT_EQ(response_rc.m_result, AssetBuilderSDK::CreateJobsResultCode::Success);
ASSERT_EQ(response_rc.m_createJobOutputs.size(), 1);
AssetBuilderSDK::JobDescriptor descriptor = response_rc.m_createJobOutputs.at(0);
ASSERT_EQ(descriptor.GetPlatformIdentifier(), "pc");
ASSERT_EQ(descriptor.m_jobKey.compare(job_key_rc), 0);
ASSERT_FALSE(descriptor.m_critical);
}
}
TEST_F(RCBuilderTest, CreateJobs_CreateSingleJobCopy_Valid)
{
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
AssetRecognizer copy;
copy.m_name = "Copy";
copy.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.copy", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard);
AssetPlatformSpec copy_spec;
copy_spec.m_extraRCParams = "copy";
copy.m_platformSpecs["pc"] = copy_spec;
configuration.m_recognizerContainer["copy"] = copy;
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
AssetBuilderSDK::CreateJobsRequest request;
AssetBuilderSDK::CreateJobsResponse response;
request.m_watchFolder = "c:\temp";
request.m_sourceFile = "test.copy";
request.m_enabledPlatforms = { AssetBuilderSDK::PlatformInfo("pc",{ "desktop", "renderer" }) };
AssetProcessor::BUILDER_ID_COPY.GetUuid(request.m_builderid);
test.CreateJobs(request, response);
ASSERT_EQ(response.m_result, AssetBuilderSDK::CreateJobsResultCode::Success);
ASSERT_EQ(response.m_createJobOutputs.size(), 1);
AssetBuilderSDK::JobDescriptor descriptor = response.m_createJobOutputs.at(0);
ASSERT_EQ(descriptor.GetPlatformIdentifier(), "pc");
ASSERT_TRUE(descriptor.m_critical);
}
TEST_F(RCBuilderTest, CreateJobs_CreateSingleJobStandardSkip_Valid)
{
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
{
AssetRecognizer skip;
skip.m_name = "Skip";
skip.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.skip", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard);
AssetPlatformSpec skip_spec;
skip_spec.m_extraRCParams = "skip";
skip.m_platformSpecs["pc"] = skip_spec;
configuration.m_recognizerContainer["skip"] = skip;
}
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
AssetBuilderSDK::CreateJobsRequest request;
AssetBuilderSDK::CreateJobsResponse response;
request.m_watchFolder = "c:\temp";
request.m_sourceFile = "test.skip";
request.m_enabledPlatforms = { AssetBuilderSDK::PlatformInfo("pc",{ "desktop", "renderer" }) };
request.m_builderid = this->GetBuilderUUID();
test.CreateJobs(request, response);
ASSERT_EQ(response.m_result, AssetBuilderSDK::CreateJobsResultCode::Success);
ASSERT_EQ(response.m_createJobOutputs.size(), 0);
}
TEST_F(RCBuilderTest, CreateJobs_CreateSingleJobStandard_Failed)
{
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
AssetRecognizer good;
good.m_name = "Good";
good.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.foo", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard);
AssetPlatformSpec good_spec;
good_spec.m_extraRCParams = "/i";
good.m_platformSpecs["pc"] = good_spec;
configuration.m_recognizerContainer["good"] = good;
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
AssetBuilderSDK::CreateJobsRequest request;
AssetBuilderSDK::CreateJobsResponse response;
request.m_watchFolder = "c:\temp";
request.m_sourceFile = "test.ccc";
request.m_enabledPlatforms = { AssetBuilderSDK::PlatformInfo("pc",{ "desktop", "renderer" }) };
request.m_builderid = this->GetBuilderUUID();
test.CreateJobs(request, response);
ASSERT_EQ(response.m_result, AssetBuilderSDK::CreateJobsResultCode::Failed);
ASSERT_EQ(response.m_createJobOutputs.size(), 0);
}
TEST_F(RCBuilderTest, CreateJobs_CreateSingleJobStandard_ShuttingDown)
{
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
AssetRecognizer good;
good.m_name = "Good";
good.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.foo", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard);
AssetPlatformSpec good_spec;
good_spec.m_extraRCParams = "/i";
good.m_platformSpecs["pc"] = good_spec;
configuration.m_recognizerContainer["good"] = good;
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
test.ShutDown();
AssetBuilderSDK::CreateJobsRequest request;
AssetBuilderSDK::CreateJobsResponse response;
request.m_watchFolder = "c:\temp";
request.m_sourceFile = "test.ccc";
request.m_enabledPlatforms = { AssetBuilderSDK::PlatformInfo("pc",{ "desktop", "renderer" }) };
request.m_builderid = this->GetBuilderUUID();
test.CreateJobs(request, response);
ASSERT_EQ(response.m_result, AssetBuilderSDK::CreateJobsResultCode::ShuttingDown);
ASSERT_EQ(response.m_createJobOutputs.size(), 0);
}
TEST_F(RCBuilderTest, CreateJobs_CreateSingleJobBadJobRequest1_Failed)
{
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
AssetRecognizer good;
good.m_name = "Good";
good.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.foo", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard);
AssetPlatformSpec good_spec;
good_spec.m_extraRCParams = "/i";
good.m_platformSpecs["pc"] = good_spec;
configuration.m_recognizerContainer["good"] = good;
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
AssetBuilderSDK::CreateJobsRequest request;
AssetBuilderSDK::CreateJobsResponse response;
request.m_sourceFile = "test.ccc";
request.m_enabledPlatforms = { AssetBuilderSDK::PlatformInfo("pc",{ "desktop", "renderer" }) };
request.m_builderid = this->GetBuilderUUID();
test.CreateJobs(request, response);
ASSERT_EQ(response.m_result, AssetBuilderSDK::CreateJobsResultCode::Failed);
ASSERT_EQ(response.m_createJobOutputs.size(), 0);
}
TEST_F(RCBuilderTest, ProcessLegacyRCJob_ProcessStandardSingleJob_Failed)
{
AZ::Uuid assetTypeUUid = AZ::Uuid::CreateRandom();
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
// Create the test job
AssetBuilderSDK::ProcessJobRequest request = CreateTestJobRequest("file.c", false, "pc", 1);
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
// Case 1: execution failed
mockRC->SetResultResultExecute(NativeLegacyRCCompiler::Result(1, true, ""));
mockRC->SetResultExecute(false);
AssetBuilderSDK::ProcessJobResponse responseCrashed;
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
test.TestProcessLegacyRCJob(request, "/i", assetTypeUUid, jobCancelListener, responseCrashed);
ASSERT_EQ(responseCrashed.m_resultCode, AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Crashed);
// case 2: result code from execution non-zero
mockRC->SetResultExecute(true);
mockRC->SetResultResultExecute(NativeLegacyRCCompiler::Result(1, false, ""));
AssetBuilderSDK::ProcessJobResponse responseFailed;
test.TestProcessLegacyRCJob(request, "/i", assetTypeUUid, jobCancelListener, responseFailed);
ASSERT_EQ(responseFailed.m_resultCode, AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Failed);
}
TEST_F(RCBuilderTest, ProcessLegacyRCJob_ProcessStandardSingleJob_Valid)
{
AZ::Uuid assetTypeUUid = AZ::Uuid::CreateRandom();
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
// Create the test job
AssetBuilderSDK::ProcessJobRequest request = CreateTestJobRequest("file.c", false, "pc");
test.AddTestFileInfo("c:\\temp\\file.a").AddTestFileInfo("c:\\temp\\file.b");
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
mockRC->SetResultResultExecute(NativeLegacyRCCompiler::Result(0, false, "c:\\temp"));
mockRC->SetResultExecute(true);
AssetBuilderSDK::ProcessJobResponse response;
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
test.TestProcessLegacyRCJob(request, "/i", assetTypeUUid, jobCancelListener, response);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Success);
ASSERT_EQ(response.m_outputProducts.size(), 2); // file.c->(file.a, file.b)
}
TEST_F(RCBuilderTest, ProcessLegacyRCJob_ProcessCopySingleJob_Valid)
{
AZStd::string name = "test";
AZ::Uuid builderUuid = AZ::Uuid::CreateRandom();
AZ::Uuid assetTypeUUid = AZ::Uuid::CreateRandom();
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
// Create the test job
AssetBuilderSDK::ProcessJobRequest request = CreateTestJobRequest("file.c", false, "pc");
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
mockRC->SetResultResultExecute(NativeLegacyRCCompiler::Result(0, false, "c:\\temp"));
mockRC->SetResultExecute(true);
AssetBuilderSDK::ProcessJobResponse response;
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
test.TestProcessCopyJob(request, assetTypeUUid, jobCancelListener, response);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Success);
ASSERT_EQ(response.m_outputProducts.size(), 1); // file.c->(file.a, file.b)
AssetBuilderSDK::JobProduct resultJobProd = response.m_outputProducts.at(0);
ASSERT_EQ(resultJobProd.m_productAssetType, assetTypeUUid);
ASSERT_EQ(resultJobProd.m_productFileName, request.m_fullPath);
}
TEST_F(RCBuilderTest, MatchTempFileToSkip_SkipRCFiles_true)
{
const char* rc_skip_fileNames[] = {
"rc_createdfiles.txt",
"rc_log.log",
"rc_log_warnings.log",
"rc_log_errors.log"
};
for (const char* filename : rc_skip_fileNames)
{
ASSERT_TRUE(AssetProcessor::InternalRecognizerBasedBuilder::MatchTempFileToSkip(filename));
}
}
TEST_F(RCBuilderTest, MatchTempFileToSkip_SkipRCFiles_false)
{
const char* rc_not_skip_fileNames[] = {
"foo.log",
"bar.txt"
};
for (const char* filename : rc_not_skip_fileNames)
{
ASSERT_FALSE(AssetProcessor::InternalRecognizerBasedBuilder::MatchTempFileToSkip(filename));
}
}
TEST_F(RCBuilderTest, ProcessJob_ProcessStandardRCSingleJob_Valid)
{
AZ::Uuid assetTypeUUid = AZ::Uuid::CreateRandom();
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
// Create a dummy test recognizer
AZ::u32 recID = test.AddTestRecognizer(this->GetBuilderID(),QString("/i"), "pc");
// Create the test job
AssetBuilderSDK::ProcessJobRequest request = CreateTestJobRequest("test.tif", false, "pc");
request.m_jobDescription.m_jobParameters[recID] = "/i";
test.AddTestFileInfo("c:\\temp\\file.a").AddTestFileInfo("c:\\temp\\file.b");
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
mockRC->SetResultResultExecute(NativeLegacyRCCompiler::Result(0, false, "c:\\temp"));
mockRC->SetResultExecute(true);
AssetBuilderSDK::ProcessJobResponse response;
test.TestProcessJob(request, response);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Success);
ASSERT_EQ(response.m_outputProducts.size(), 2); // file.c->(file.a, file.b)
}
TEST_F(RCBuilderTest, ProcessJob_ProcessStandardRCSingleJob_Failed)
{
AZ::Uuid assetTypeUUid = AZ::Uuid::CreateRandom();
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
// Create a dummy test recognizer
AZ::u32 recID = test.AddTestRecognizer(this->GetBuilderID(), QString("/i"), "pc");
// Create the test job
AssetBuilderSDK::ProcessJobRequest request = CreateTestJobRequest("test.tif", false, "pc");
request.m_jobDescription.m_jobParameters[recID] = "/i";
test.AddTestFileInfo("c:\\temp\\file.a").AddTestFileInfo("c:\\temp\\file.b");
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
mockRC->SetResultResultExecute(NativeLegacyRCCompiler::Result(1, false, "c:\\temp"));
mockRC->SetResultExecute(true);
AssetBuilderSDK::ProcessJobResponse response;
test.TestProcessJob(request, response);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Failed);
}
TEST_F(RCBuilderTest, ProcessJob_ProcessStandardCopySingleJob_Valid)
{
AZ::Uuid assetTypeUUid = AZ::Uuid::CreateRandom();
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
// Create a dummy test recognizer
AZ::u32 recID = test.AddTestRecognizer(this->GetBuilderID(), QString("copy"), "pc");
// Create the test job
AssetBuilderSDK::ProcessJobRequest request = CreateTestJobRequest("test.tif", true, "pc");
request.m_jobDescription.m_jobParameters[recID] = "copy";
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
mockRC->SetResultExecute(true);
AssetBuilderSDK::ProcessJobResponse response;
test.TestProcessJob(request, response);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Success);
ASSERT_EQ(response.m_outputProducts.size(), 1); // test.
ASSERT_TRUE(response.m_outputProducts[0].m_productFileName.find("test.tif") != AZStd::string::npos);
}
TEST_F(RCBuilderTest, ProcessJob_ProcessStandardSkippedSingleJob_Invalid)
{
AZ::Uuid assetTypeUUid = AZ::Uuid::CreateRandom();
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
// Create a dummy test recognizer
AZ::u32 recID = test.AddTestRecognizer(this->GetBuilderID(), QString("skip"), "pc");
// Create the test job
AssetBuilderSDK::ProcessJobRequest request = CreateTestJobRequest("test.tif", true, "pc");
request.m_jobDescription.m_jobParameters[recID] = "copy";
bool initialization_result = test.Initialize(configuration);
ASSERT_TRUE(initialization_result);
mockRC->SetResultExecute(true);
AssetBuilderSDK::ProcessJobResponse response;
test.TestProcessJob(request, response);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Failed);
}
TEST_F(RCBuilderTest, TestProcessRCResultFolder_LegacySystem)
{
TestInternalRecognizerBasedBuilder test(new MockRCCompiler());
test.AddTestFileInfo("file.dds")
.AddTestFileInfo("file.caf")
.AddTestFileInfo("file.png")
.AddTestFileInfo("rc_createdfiles.txt")
.AddTestFileInfo("rc_log.log")
.AddTestFileInfo("rc_log_warnings.log")
.AddTestFileInfo("rc_log_errors.log")
.AddTestFileInfo("ProcessJobRequest.xml")
.AddTestFileInfo("ProcessJobResponse.xml");
AZ::Uuid productGUID = AZ::Uuid("{60554E3C-D8D5-4429-AC77-740F0ED46193}");
AssetBuilderSDK::ProcessJobResponse response;
test.TestProcessRCResultFolder("c:\\temp", productGUID, false, response);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Success);
// we expect it to have ignored most of the file cruft.
ASSERT_EQ(response.m_outputProducts.size(), 3);
AZStd::string fileJoined;
AzFramework::StringFunc::Path::Join("c:\\temp", "file.dds", fileJoined);
ASSERT_EQ(response.m_outputProducts[0].m_productFileName, fileJoined);
ASSERT_EQ(response.m_outputProducts[0].m_productAssetType, productGUID);
ASSERT_EQ(response.m_outputProducts[0].m_productSubID, 0x00000000);
ASSERT_EQ(response.m_outputProducts[0].m_legacySubIDs.size(), 0);
AzFramework::StringFunc::Path::Join("c:\\temp", "file.caf", fileJoined);
ASSERT_EQ(response.m_outputProducts[1].m_productFileName, fileJoined);
ASSERT_EQ(response.m_outputProducts[1].m_productAssetType, productGUID);
ASSERT_EQ(response.m_outputProducts[1].m_productSubID, (AZ_CRC("file.caf", 0x91277b80) & 0x0000FFFF)); // legacy subids are just the lower 16 bits of the crc of filename.
ASSERT_EQ(response.m_outputProducts[1].m_legacySubIDs.size(), 0);
AzFramework::StringFunc::Path::Join("c:\\temp", "file.png", fileJoined);
ASSERT_EQ(response.m_outputProducts[2].m_productFileName, fileJoined);
ASSERT_EQ(response.m_outputProducts[2].m_productAssetType, productGUID);
ASSERT_EQ(response.m_outputProducts[2].m_productSubID, (AZ_CRC("file.png", 0x7fd84af0) & 0x0000FFFF));
ASSERT_EQ(response.m_outputProducts[2].m_legacySubIDs.size(), 0);
}
TEST_F(RCBuilderTest, TestProcessRCResultFolder_Fail_Fail)
{
TestInternalRecognizerBasedBuilder test(new MockRCCompiler());
AssetBuilderSDK::ProcessJobResponse response;
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
AZ::Uuid productGUID = AZ::Uuid("{60554E3C-D8D5-4429-AC77-740F0ED46193}");
test.TestProcessRCResultFolder("c:\\temp", productGUID, true, response);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResult_Failed);
}
TEST_F(RCBuilderTest, TestProcessRCResultFolder_Succeed_NothingBuilt)
{
TestInternalRecognizerBasedBuilder test(new MockRCCompiler());
AssetBuilderSDK::ProcessJobResponse response;
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
AZ::Uuid productGUID = AZ::Uuid("{60554E3C-D8D5-4429-AC77-740F0ED46193}");
test.TestProcessRCResultFolder("c:\\temp", productGUID, true, response);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResult_Success);
}
TEST_F(RCBuilderTest, TestProcessRCResultFolder_Fail_BadName)
{
TestInternalRecognizerBasedBuilder test(new MockRCCompiler());
AssetBuilderSDK::ProcessJobResponse response;
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
AZ::Uuid productGUID = AZ::Uuid("{60554E3C-D8D5-4429-AC77-740F0ED46193}");
// note: empty name on next line
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("", productGUID, 1234));
test.TestProcessRCResultFolder("c:\\temp", productGUID, true, response);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResult_Failed);
}
TEST_F(RCBuilderTest, TestProcessRCResultFolder_Fail_DuplicateFile)
{
m_errorAbsorber->m_debugMessages = true;
TestInternalRecognizerBasedBuilder test(new MockRCCompiler());
AssetBuilderSDK::ProcessJobResponse response;
test.AddTestFileInfo("file.dds");
AZ::Uuid productGUID = AZ::Uuid("{60554E3C-D8D5-4429-AC77-740F0ED46193}");
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("file.dds", productGUID, 1234));
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("file.dds", productGUID, 5679));
test.TestProcessRCResultFolder("c:\\temp", productGUID, true, response);
m_errorAbsorber->AssertErrors(1);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResult_Failed);
}
TEST_F(RCBuilderTest, TestProcessRCResultFolder_Fail_DuplicateSubID)
{
TestInternalRecognizerBasedBuilder test(new MockRCCompiler());
AssetBuilderSDK::ProcessJobResponse response;
test.AddTestFileInfo("file.dds")
.AddTestFileInfo("file.caf");
AZ::Uuid productGUID = AZ::Uuid("{60554E3C-D8D5-4429-AC77-740F0ED46193}");
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("file.dds", productGUID, 1234));
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("file.caf", productGUID, 1234));
test.TestProcessRCResultFolder("c:\\temp", productGUID, true, response);
ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 1);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResult_Failed);
}
TEST_F(RCBuilderTest, TestProcessRCResultFolder_WithResponseFromRC)
{
TestInternalRecognizerBasedBuilder test(new MockRCCompiler());
test.AddTestFileInfo("file.dds")
.AddTestFileInfo("file.caf")
.AddTestFileInfo("file.png")
.AddTestFileInfo("rc_createdfiles.txt")
.AddTestFileInfo("rc_log.log")
.AddTestFileInfo("rc_log_warnings.log")
.AddTestFileInfo("rc_log_errors.log")
.AddTestFileInfo("ProcessJobRequest.xml")
.AddTestFileInfo("ProcessJobResponse.xml");
AZ::Uuid productGUID = AZ::Uuid::CreateNull(); // this is to make sure that it doesn't matter what we pass in
AZ::Uuid actualGUID = AZ::Uuid("{60554E3C-D8D5-4429-AC77-740F0ED46193}");
AssetBuilderSDK::ProcessJobResponse response;
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("file.dds", actualGUID, 1234));
response.m_outputProducts.back().m_legacySubIDs.push_back(3333);
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct("file.caf", actualGUID, 3456));
response.m_outputProducts.back().m_legacySubIDs.push_back(2222);
response.m_outputProducts.back().m_legacySubIDs.push_back((AZ_CRC("file.caf", 0x91277b80) & 0x0000FFFF)); // push back the existing one to make sure no dupes.
response.m_resultCode = AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Success;
// in this test we pretend the response was actually populated by the builder and make sure it populates the legacy IDs correctly
// 1. there should actually BE legacy IDs
// 2. there should be no duplicate IDs (legacy IDs should not duplicate ACTUAL ids)
// 3. there should be no duplicate Legacy IDs (legacy IDs should not duplicate each other)
// 4. if we provide legacy Ids, they should be used in addition to the automatic ones.
test.TestProcessRCResultFolder("c:\\temp", productGUID, true, response);
ASSERT_EQ(response.m_resultCode, AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Success);
// we expect it to only have accepted the products we specified.
ASSERT_EQ(response.m_outputProducts.size(), 2);
AZStd::string fileJoined;
AzFramework::StringFunc::Path::Join("c:\\temp", "file.dds", fileJoined);
ASSERT_EQ(response.m_outputProducts[0].m_productFileName, fileJoined);
ASSERT_EQ(response.m_outputProducts[0].m_productAssetType, actualGUID);
ASSERT_EQ(response.m_outputProducts[0].m_productSubID, 1234);
ASSERT_EQ(response.m_outputProducts[0].m_legacySubIDs.size(), 2); // it must include our new one AND the zero that it would have generated before.
ASSERT_EQ(response.m_outputProducts[0].m_legacySubIDs[0], 3333);
ASSERT_EQ(response.m_outputProducts[0].m_legacySubIDs[1], 0);
AzFramework::StringFunc::Path::Join("c:\\temp", "file.caf", fileJoined);
ASSERT_EQ(response.m_outputProducts[1].m_productFileName, fileJoined);
ASSERT_EQ(response.m_outputProducts[1].m_productAssetType, actualGUID);
ASSERT_EQ(response.m_outputProducts[1].m_productSubID, 3456); // legacy subids are just the lower 16 bits of the crc of filename.
ASSERT_EQ(response.m_outputProducts[1].m_legacySubIDs.size(), 2); // we only expect the one legacy, no dupes!
ASSERT_EQ(response.m_outputProducts[1].m_legacySubIDs[0], 2222);
ASSERT_EQ(response.m_outputProducts[1].m_legacySubIDs[1], (AZ_CRC("file.caf", 0x91277b80) & 0x0000FFFF));
}
class MockBuilderListener : public AssetBuilderSDK::AssetBuilderBus::Handler
{
public:
void RegisterBuilderInformation(const AssetBuilderSDK::AssetBuilderDesc& builderDesc) override
{
m_wasCalled = true;
m_result = builderDesc;
}
bool m_wasCalled = false;
AssetBuilderSDK::AssetBuilderDesc m_result;
};
class RCBuilderFingerprintTest
: public RCBuilderTest
{
public:
// A utility function which feeds in the version and asset type to the builder, fingerprints it, and returns the fingerprint
AZStd::string BuildFingerprint(int versionNumber, AZ::Uuid builderProductType)
{
MockRCCompiler* mockRC = new MockRCCompiler();
TestInternalRecognizerBasedBuilder test(mockRC);
MockRecognizerConfiguration configuration;
AssetPlatformSpec good_spec;
good_spec.m_extraRCParams = "/i";
AssetRecognizer good;
good.m_name = "Good";
good.m_version = versionNumber;
good.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.foo", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard);
good.m_platformSpecs["pc"] = good_spec;
good.m_productAssetType = builderProductType;
configuration.m_recognizerContainer["good"] = good;
MockBuilderListener listener;
listener.BusConnect();
bool initialization_result = test.Initialize(configuration);
listener.BusDisconnect();
EXPECT_TRUE(listener.m_wasCalled);
EXPECT_TRUE(initialization_result);
EXPECT_STRNE(listener.m_result.m_analysisFingerprint.c_str(), "");
return listener.m_result.m_analysisFingerprint;
}
};
TEST_F(RCBuilderFingerprintTest, DifferentVersion_Has_DifferentAnalysisFingerprint)
{
AZ::Uuid uuid1 = AZ::Uuid::CreateRandom();
AZStd::string analysisFingerprint1 = BuildFingerprint(1, uuid1);
AZStd::string analysisFingerprint2 = BuildFingerprint(2, uuid1);
EXPECT_STRNE(analysisFingerprint1.c_str(), analysisFingerprint2.c_str());
}
TEST_F(RCBuilderFingerprintTest, DifferentAssetType_Has_DifferentAnalysisFingerprint)
{
AZ::Uuid uuid1 = AZ::Uuid::CreateRandom();
AZ::Uuid uuid2 = AZ::Uuid::CreateRandom();
AZStd::string analysisFingerprint1 = BuildFingerprint(1, uuid1);
AZStd::string analysisFingerprint2 = BuildFingerprint(1, uuid2);
EXPECT_STRNE(analysisFingerprint1.c_str(), analysisFingerprint2.c_str());
}
TEST_F(RCBuilderFingerprintTest, DifferentAssetTypeAndVersion_Has_DifferentAnalysisFingerprint)
{
AZ::Uuid uuid1 = AZ::Uuid::CreateRandom();
AZ::Uuid uuid2 = AZ::Uuid::CreateRandom();
AZStd::string analysisFingerprint1 = BuildFingerprint(1, uuid1);
AZStd::string analysisFingerprint2 = BuildFingerprint(2, uuid2);
EXPECT_STRNE(analysisFingerprint1.c_str(), analysisFingerprint2.c_str());
}
TEST_F(RCBuilderFingerprintTest, SameVersionAndSameType_Has_SameAnalysisFingerprint)
{
AZ::Uuid uuid1 = AZ::Uuid::CreateRandom();
AZStd::string analysisFingerprint1 = BuildFingerprint(1, uuid1);
AZStd::string analysisFingerprint2 = BuildFingerprint(1, uuid1);
EXPECT_STREQ(analysisFingerprint1.c_str(), analysisFingerprint2.c_str());
}
@@ -0,0 +1,250 @@
/*
* 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 <AzTest/AzTest.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <qcoreapplication.h>
#include "../../utilities/assetUtils.h"
#include "../../resourcecompiler/RCBuilder.h"
#include "native/tests/AssetProcessorTest.h"
using namespace AssetProcessor;
extern const BuilderIdAndName BUILDER_ID_COPY;
extern const BuilderIdAndName BUILDER_ID_RC;
extern const BuilderIdAndName BUILDER_ID_SKIP;
class MockRCCompiler
: public AssetProcessor::RCCompiler
{
public:
MockRCCompiler()
: m_executeResultResult(0, false, "c:\temp")
{
}
bool Initialize([[maybe_unused]] const QString& systemRoot, [[maybe_unused]] const QString& rcExecutableFullPath) override
{
m_initialize++;
return m_initializeResult;
}
bool Execute([[maybe_unused]] const QString& inputFile, [[maybe_unused]] const QString& watchFolder, [[maybe_unused]] const QString& platformIdentifier, [[maybe_unused]] const QString& params, [[maybe_unused]] const QString& dest,
[[maybe_unused]] const AssetBuilderSDK::JobCancelListener* jobCancelListener, Result& result) const override
{
m_execute++;
result = m_executeResultResult;
return m_executeResult;
}
void RequestQuit() override
{
m_request_quit++;
}
void ResetCounters()
{
this->m_initialize = 0;
this->m_execute = 0;
this->m_request_quit = 0;
}
void SetResultInitialize(bool result)
{
m_initializeResult = result;
}
void SetResultExecute(bool result)
{
m_executeResult = result;
}
void SetResultResultExecute(Result result)
{
m_executeResultResult = result;
}
bool m_initializeResult = true;
bool m_executeResult = true;
Result m_executeResultResult;
mutable int m_initialize = 0;
mutable int m_execute = 0;
mutable int m_request_quit = 0;
};
struct MockRecognizerConfiguration
: public RecognizerConfiguration
{
const RecognizerContainer& GetAssetRecognizerContainer() const override
{
return m_recognizerContainer;
}
const ExcludeRecognizerContainer& GetExcludeAssetRecognizerContainer() const override
{
return m_excludeContainer;
}
RecognizerContainer m_recognizerContainer;
ExcludeRecognizerContainer m_excludeContainer;
};
struct TestInternalRecognizerBasedBuilder
: public InternalRecognizerBasedBuilder
{
TestInternalRecognizerBasedBuilder(RCCompiler* rcCompiler = nullptr)
: InternalRecognizerBasedBuilder()
{
if (rcCompiler != nullptr)
{
m_rcCompiler.reset(rcCompiler);
}
}
bool FindRC([[maybe_unused]] QString& rcPathOut) override
{
return true;
}
QFileInfoList GetFilesInDirectory([[maybe_unused]] const QString& directoryPath) override
{
QFileInfoList mockFileInfoList;
mockFileInfoList.append(m_testFileInfo);
return mockFileInfoList;
}
bool SaveProcessJobRequestFile(const char* /*requestFileDir*/, const char* /*requestFileName*/, const AssetBuilderSDK::ProcessJobRequest& /*request*/) override
{
m_savedProcessJob = true;
return true;
}
// returns false only if there is a critical failure.
bool LoadProcessJobResponseFile(const char* /*responseFileDir*/, const char* /*responseFileName*/, AssetBuilderSDK::ProcessJobResponse& /*response*/, bool& /*responseLoaded*/) override
{
m_loadedProcessJob = true;
return true;
}
void TestProcessJob(const AssetBuilderSDK::ProcessJobRequest& request,
AssetBuilderSDK::ProcessJobResponse& response)
{
InternalRecognizerBasedBuilder::ProcessJob(request, response);
}
void TestProcessLegacyRCJob(const AssetBuilderSDK::ProcessJobRequest& request,
QString rcParam,
AZ::Uuid productAssetType,
const AssetBuilderSDK::JobCancelListener& jobCancelListener,
AssetBuilderSDK::ProcessJobResponse& response)
{
InternalRecognizerBasedBuilder::ProcessLegacyRCJob(request, rcParam, productAssetType, jobCancelListener, response);
}
void TestProcessCopyJob(const AssetBuilderSDK::ProcessJobRequest& request,
AZ::Uuid productAssetType,
const AssetBuilderSDK::JobCancelListener& jobCancelListener,
AssetBuilderSDK::ProcessJobResponse& response)
{
const bool outputProductDependency = false;
InternalRecognizerBasedBuilder::ProcessCopyJob(request, productAssetType, outputProductDependency, jobCancelListener, response);
}
TestInternalRecognizerBasedBuilder& AddTestFileInfo(const QString& testFileFullPath)
{
QFileInfo testFileInfo(testFileFullPath);
m_testFileInfo.push_back(testFileInfo);
return *this;
}
AZ::u32 AddTestRecognizer(QString builderID, QString extraRCParam, QString platformString)
{
// Create a dummy test recognizer
AssetBuilderSDK::FilePatternMatcher patternMatcher;
QString versionZero("0");
AZ::Data::AssetType productAssetType = AZ::Uuid::CreateRandom();
AssetRecognizer baseAssetRecognizer(QString("test-").append(extraRCParam), false, 1, false, false, patternMatcher, versionZero, productAssetType, false);
QHash<QString, AssetPlatformSpec> assetPlatformSpecByPlatform;
AssetPlatformSpec assetSpec;
assetSpec.m_extraRCParams = extraRCParam;
assetPlatformSpecByPlatform[platformString] = assetSpec;
InternalAssetRecognizer* pTestInternalRecognizer = new InternalAssetRecognizer(baseAssetRecognizer, builderID, assetPlatformSpecByPlatform);
this->m_assetRecognizerDictionary[pTestInternalRecognizer->m_paramID] = pTestInternalRecognizer;
return pTestInternalRecognizer->m_paramID;
}
void TestProcessRCResultFolder(const QString &dest, const AZ::Uuid& productAssetType, bool responseFromRCCompiler, AssetBuilderSDK::ProcessJobResponse &response)
{
ProcessRCResultFolder(dest, productAssetType, responseFromRCCompiler, response);
}
QList<QFileInfo> m_testFileInfo;
bool m_savedProcessJob = false;
bool m_loadedProcessJob = false;
};
class RCBuilderTest
: public AssetProcessor::AssetProcessorTest
{
int m_argc;
char** m_argv;
QCoreApplication* m_qApp = nullptr;
public:
RCBuilderTest()
: m_argc(0)
, m_argv(0)
{
m_qApp = new QCoreApplication(m_argc, m_argv);
}
virtual ~RCBuilderTest()
{
delete m_qApp;
}
AZ::Uuid GetBuilderUUID() const
{
AZ::Uuid rcUuid;
AssetProcessor::BUILDER_ID_RC.GetUuid(rcUuid);
return rcUuid;
}
AZStd::string GetBuilderName() const
{
return AZStd::string(AssetProcessor::BUILDER_ID_RC.GetName().toUtf8().data());
}
QString GetBuilderID() const
{
return AssetProcessor::BUILDER_ID_RC.GetId();
}
AssetBuilderSDK::ProcessJobRequest CreateTestJobRequest(const AZStd::string& testFileName, bool critical, QString platform, AZ::s64 jobId = 0)
{
AssetBuilderSDK::ProcessJobRequest request;
request.m_builderGuid = this->GetBuilderUUID();
request.m_sourceFile = testFileName;
request.m_fullPath = AZStd::string("c:\\temp\\") + testFileName;
request.m_tempDirPath = "c:\\temp";
request.m_jobDescription.m_critical = critical;
request.m_jobDescription.SetPlatformIdentifier(platform.toUtf8().constData());
request.m_jobId = jobId;
return request;
}
};
@@ -0,0 +1,300 @@
/*
* 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 "RCControllerTest.h"
#include "native/resourcecompiler/rccontroller.h"
#include "AzCore/std/parallel/binary_semaphore.h"
TEST_F(RCcontrollerTest, CompileGroupCreatedWithUnknownStatusForFailedJobs)
{
//Strategy Add a failed job to the job queue list and than ask the rc controller to request compile, it should emit unknown status
using namespace AssetProcessor;
// we have to initialize this to something other than Assetstatus_Unknown here because later on we will be testing the value of assetstatus
AzFramework::AssetSystem::AssetStatus assetStatus = AzFramework::AssetSystem::AssetStatus_Failed;
RCController rcController;
QObject::connect(&rcController, &RCController::CompileGroupCreated,
[&assetStatus]([[maybe_unused]] AssetProcessor::NetworkRequestID groupID, AzFramework::AssetSystem::AssetStatus status)
{
assetStatus = status;
}
);
RCJobListModel* rcJobListModel = rcController.GetQueueModel();
RCJob* job = new RCJob(rcJobListModel);
AssetProcessor::JobDetails jobDetails;
jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = jobDetails.m_jobEntry.m_databaseSourceName = "somepath/failed.dds";
jobDetails.m_jobEntry.m_platformInfo = { "pc", {"desktop", "renderer"} };
jobDetails.m_jobEntry.m_jobKey = "Compile Stuff";
job->SetState(RCJob::failed);
job->Init(jobDetails);
rcJobListModel->addNewJob(job);
// Exact Match
NetworkRequestID requestID(1, 1234);
rcController.OnRequestCompileGroup(requestID, "pc", "somepath/failed.dds", AZ::Data::AssetId());
ASSERT_TRUE(assetStatus == AzFramework::AssetSystem::AssetStatus_Unknown);
assetStatus = AzFramework::AssetSystem::AssetStatus_Failed;
// Broader Match
rcController.OnRequestCompileGroup(requestID, "pc", "somepath", AZ::Data::AssetId() );
ASSERT_TRUE(assetStatus == AzFramework::AssetSystem::AssetStatus_Unknown);
}
class RCcontrollerTest_Cancellation
: public RCcontrollerTest
{
public:
RCcontrollerTest_Cancellation()
{
}
virtual ~RCcontrollerTest_Cancellation()
{
}
void SetUp() override
{
RCcontrollerTest::SetUp();
using namespace AssetProcessor;
m_rcController.reset(new RCController());
m_rcController->SetDispatchPaused(true);
m_rcJobListModel = m_rcController->GetQueueModel();
{
RCJob* job = new RCJob(m_rcJobListModel);
AssetProcessor::JobDetails jobDetails;
jobDetails.m_jobEntry.m_computedFingerprint = 1;
jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = jobDetails.m_jobEntry.m_databaseSourceName = "somepath/failed.dds";
jobDetails.m_jobEntry.m_platformInfo = { "ios",{ "mobile", "renderer" } };
jobDetails.m_jobEntry.m_jobRunKey = 1;
jobDetails.m_jobEntry.m_jobKey = "tiff";
job->SetState(RCJob::JobState::pending);
job->Init(jobDetails);
m_rcJobListModel->addNewJob(job);
}
{
RCJob* job = new RCJob(m_rcJobListModel);
// note that Init() is a move operation. we cannot reuse jobDetails.
AssetProcessor::JobDetails jobDetails;
jobDetails.m_jobEntry.m_computedFingerprint = 1;
jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = jobDetails.m_jobEntry.m_databaseSourceName = "somepath/failed.dds";
jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
jobDetails.m_jobEntry.m_jobRunKey = 2;
jobDetails.m_jobEntry.m_jobKey = "tiff";
job->SetState(RCJob::JobState::pending);
job->Init(jobDetails);
m_rcJobListModel->addNewJob(job);
m_rcJobListModel->markAsStarted(job);
m_rcJobListModel->markAsProcessing(job); // job is now "in flight"
}
}
void TearDown() override
{
m_rcJobListModel = nullptr;
m_rcController.reset();
RCcontrollerTest::TearDown();
}
AZStd::unique_ptr<AssetProcessor::RCController> m_rcController;
AssetProcessor::RCJobListModel* m_rcJobListModel = nullptr; // convenience pointer into m_rcController->GetQueueModel()
};
TEST_F(RCcontrollerTest_Cancellation, JobSubmitted_SameFingerprint_DoesNotCancelTheJob)
{
// submit a new job for the same details as the already running one.
{
AssetProcessor::JobDetails jobDetails;
jobDetails.m_jobEntry.m_computedFingerprint = 1; // same as above in SetUp
jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = jobDetails.m_jobEntry.m_databaseSourceName = "somepath/failed.dds";
jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
jobDetails.m_jobEntry.m_jobKey = "tiff";
jobDetails.m_jobEntry.m_jobRunKey = 3;
m_rcController->JobSubmitted(jobDetails);
}
for (int idx = 0; idx < m_rcJobListModel->itemCount(); idx++)
{
// neither job should be cancelled.
AssetProcessor::RCJob* rcJob = m_rcJobListModel->getItem(idx);
ASSERT_TRUE(rcJob->GetState() != AssetProcessor::RCJob::JobState::cancelled);
}
}
TEST_F(RCcontrollerTest_Cancellation, JobSubmitted_DifferentFingerprint_CancelsTheJob_OnlyIfInProgress)
{
// submit a new job for the same details as the already running one.
{
AssetProcessor::JobDetails jobDetails;
jobDetails.m_jobEntry.m_computedFingerprint = 2; // different from setup.
jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = jobDetails.m_jobEntry.m_databaseSourceName = "somepath/failed.dds";
jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
jobDetails.m_jobEntry.m_jobKey = "tiff";
jobDetails.m_jobEntry.m_jobRunKey = 3;
m_rcController->JobSubmitted(jobDetails);
}
for (int idx = 0; idx < m_rcJobListModel->itemCount(); idx++)
{
// neither job should be cancelled.
AssetProcessor::RCJob* rcJob = m_rcJobListModel->getItem(idx);
if (rcJob->GetJobEntry().m_jobRunKey == 2)
{
// the one with run key 2 should have been cancelled and replaced with run key 3
ASSERT_TRUE(rcJob->GetState() == AssetProcessor::RCJob::JobState::cancelled);
}
else
{
// the other one should have been left alone since it had not yet begun.
ASSERT_TRUE(rcJob->GetState() != AssetProcessor::RCJob::JobState::cancelled);
}
}
}
class RCcontrollerTest_Simple
: public RCcontrollerTest
{
public:
void SetUp() override
{
RCcontrollerTest::SetUp();
using namespace AssetProcessor;
m_rcController.reset(new RCController(/*minJobs*/1, /*maxJobs*/1));
m_rcController->SetDispatchPaused(false);
m_rcJobListModel = m_rcController->GetQueueModel();
qRegisterMetaType<AssetBuilderSDK::ProcessJobResponse>("ProcessJobResponse");
QObject::connect(m_rcController.get(), &RCController::BecameIdle, [this]()
{
m_wait.release();
});
}
void TearDown() override
{
m_rcJobListModel = nullptr;
m_rcController.reset();
RCcontrollerTest::TearDown();
}
void SubmitJob();
AZStd::binary_semaphore m_wait;
AZStd::unique_ptr<AssetProcessor::RCController> m_rcController;
AssetProcessor::RCJobListModel* m_rcJobListModel = nullptr; // convenience pointer into m_rcController->GetQueueModel()
};
void RCcontrollerTest_Simple::SubmitJob()
{
using namespace AssetBuilderSDK;
{
AssetProcessor::JobDetails jobDetails;
jobDetails.m_jobEntry.m_computedFingerprint = 123;
jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = jobDetails.m_jobEntry.m_databaseSourceName = "somepath/a.dds";
jobDetails.m_jobEntry.m_platformInfo = { "pc",{ "desktop", "renderer" } };
jobDetails.m_jobEntry.m_jobKey = "tiff";
jobDetails.m_jobEntry.m_jobRunKey = 3;
jobDetails.m_assetBuilderDesc.m_processJobFunction = []([[maybe_unused]] const ProcessJobRequest& request, ProcessJobResponse& response)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
};
m_rcController->JobSubmitted(jobDetails);
}
// Numbers are a bit arbitrary but this should result in a max wait time of 5s
int retryCount = 100;
do
{
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
} while (m_wait.try_acquire_for(AZStd::chrono::milliseconds(5)) == false && --retryCount > 0);
ASSERT_GT(retryCount, 0);
}
// This is a regresssion test to ensure the rccontroller can handle multiple jobs for the same file being completed before
// the APM has a chance to send OnFinishedProcesssingJob events
TEST_F(RCcontrollerTest_Simple, SameJobIsCompletedMultipleTimes_CompletesWithoutError)
{
using namespace AssetProcessor;
AZStd::vector<JobEntry> jobEntries;
QObject::connect(m_rcController.get(), &RCController::FileCompiled, [&jobEntries](JobEntry entry, AssetBuilderSDK::ProcessJobResponse response)
{
jobEntries.push_back(entry);
});
SubmitJob();
SubmitJob();
ASSERT_EQ(jobEntries.size(), 2);
for (const JobEntry& entry : jobEntries)
{
m_rcController->OnAddedToCatalog(entry);
}
ASSERT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 4); // Expected that there are 4 errors related to the files not existing on disk. Error message: GenerateFingerprint was called but no input files were requested for fingerprinting.
ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
}
// makes sure to expose parts of RCJob to the unit test
class TestRCJob : public AssetProcessor::RCJob
{
friend class GTEST_TEST_CLASS_NAME_(RCcontrollerTest, BuilderSDK_API_ProcessJob_HasValidParameters_WithOutputFolder);
public:
explicit TestRCJob(QObject* parent = 0) : AssetProcessor::RCJob(parent) {};
};
TEST_F(RCcontrollerTest, BuilderSDK_API_ProcessJob_HasValidParameters_WithOutputFolder)
{
// this test makes sure that the BuilderSDK API is not exposed to any database internals.
AZ::Uuid sourceUUID = AZ::Uuid::CreateRandom();
AZ::Uuid builderGuid = AZ::Uuid::CreateRandom();
AssetBuilderSDK::ProcessJobRequest req;
{
// note that this scope is intentional. job.Init(jobDetails) below is actually a by-ref operation that destroys JobDetails in the process.
AssetProcessor::JobDetails jobDetails;
// the crux of this test: the database source name differs from the relative path to watch folder:
jobDetails.m_jobEntry.m_pathRelativeToWatchFolder = "SomeThing.tif"; // case sensitive
// Note that while this is an OS-SPECIFIC path, this unit test does not actually invoke the file system at all
// and only operates on in-memory structures, so it should work on every platform.
jobDetails.m_jobEntry.m_watchFolderPath = "c:/test/a/B/c"; // just to make sure case is preserved
jobDetails.m_jobEntry.m_databaseSourceName = "somepath/SomeThing.tif"; // case sensitive but outputprefixes are generally lowcase
jobDetails.m_jobEntry.m_sourceFileUUID = sourceUUID;
jobDetails.m_jobEntry.m_platformInfo = { "ios",{ "mobile", "renderer" } };
jobDetails.m_jobEntry.m_jobRunKey = 1;
jobDetails.m_jobEntry.m_jobKey = "tiff";
jobDetails.m_jobEntry.m_builderGuid = builderGuid;
TestRCJob job;
job.Init(jobDetails);
job.PopulateProcessJobRequest(req);
}
EXPECT_STREQ(req.m_sourceFile.c_str(), "SomeThing.tif");
EXPECT_STREQ(req.m_watchFolder.c_str(), "c:/test/a/B/c");
// the crux of the test, tested: make sure that it does not contain 'somepath' in there just becuase its part of the Database Source Name.
EXPECT_STREQ(req.m_fullPath.c_str(), "c:/test/a/B/c/SomeThing.tif");
EXPECT_EQ(req.m_builderGuid, builderGuid);
EXPECT_TRUE(req.m_platformInfo.HasTag("renderer"));
EXPECT_TRUE(req.m_platformInfo.HasTag("mobile"));
EXPECT_STREQ(req.m_platformInfo.m_identifier.c_str(), "ios");
EXPECT_EQ(req.m_sourceFileUUID, sourceUUID);
}
@@ -0,0 +1,42 @@
/*
* 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 "native/tests/AssetProcessorTest.h"
#include <QCoreApplication>
#include "native/assetprocessor.h"
#include <AzFramework/Asset/AssetSystemTypes.h>
class RCcontrollerTest
: public AssetProcessor::AssetProcessorTest
{
public:
RCcontrollerTest()
: m_argc(0)
, m_argv(0)
{
m_qApp = new QCoreApplication(m_argc, m_argv);
qRegisterMetaType<AzFramework::AssetSystem::AssetStatus>("AzFramework::AssetSystem::AssetStatus");
qRegisterMetaType<AssetProcessor::NetworkRequestID>("NetworkRequestID");
}
virtual ~RCcontrollerTest()
{
delete m_qApp;
}
private:
int m_argc;
char** m_argv;
QCoreApplication* m_qApp;
};
@@ -0,0 +1,290 @@
/*
* 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 "RCJobTest.h"
#include <native/tests/AssetProcessorTest.h>
#include <native/resourcecompiler/rcjob.h>
namespace UnitTests
{
using namespace testing;
using ::testing::NiceMock;
using namespace AssetProcessor;
using namespace AssetBuilderSDK;
class MockDiskSpaceResponder : public DiskSpaceInfoBus::Handler
{
public:
MOCK_METHOD3(CheckSufficientDiskSpace, bool(const QString&, qint64, bool));
};
class IgnoreNotifyTracker : public ProcessingJobInfoBus::Handler
{
public:
// Will notify other systems which old product is just about to get removed from the cache
// before we copy the new product instead along.
void BeginCacheFileUpdate(const char* productPath) override
{
m_capturedStartPaths.push_back(productPath);
}
// Will notify other systems which product we are trying to copy in the cache
// along with status of whether that copy succeeded or failed.
void EndCacheFileUpdate(const char* productPath, bool /*queueAgainForProcessing*/) override
{
m_capturedStopPaths.push_back(productPath);
}
AZStd::vector<AZStd::string> m_capturedStartPaths;
AZStd::vector<AZStd::string> m_capturedStopPaths;
};
class RCJobTest : public AssetProcessorTest
{
public:
void SetUp() override
{
AssetProcessorTest::SetUp();
m_data.reset(new StaticData());
m_data->tempDirPath = QDir(m_data->m_tempDir.path());
m_data->m_absolutePathToTempInputFolder = m_data->tempDirPath.absoluteFilePath("InputFolder").toUtf8().constData();
// note that the case of OutputFolder is intentionally upper/lower case becuase
// while files inside the output folder should be lowercased, the path to there should not be lowercased by RCJob.
m_data->m_absolutePathToTempOutputFolder = m_data->tempDirPath.absoluteFilePath("OutputFolder").toUtf8().constData();
m_data->tempDirPath.mkpath(QString::fromUtf8(m_data->m_absolutePathToTempInputFolder.c_str()));
m_data->m_diskSpaceResponder.BusConnect();
m_data->m_notifyTracker.BusConnect();
// this can be overridden in each test but if you don't override it, then this fixture will do it.
ON_CALL(m_data->m_diskSpaceResponder, CheckSufficientDiskSpace(_, _, _))
.WillByDefault(Return(true));
}
void TearDown() override
{
m_data->m_diskSpaceResponder.BusDisconnect();
m_data->m_notifyTracker.BusDisconnect();
m_data.reset();
AssetProcessorTest::TearDown();
}
protected:
struct StaticData
{
QTemporaryDir m_tempDir;
QDir tempDirPath;
AZStd::string m_absolutePathToTempInputFolder;
AZStd::string m_absolutePathToTempOutputFolder;
NiceMock<MockDiskSpaceResponder> m_diskSpaceResponder;
IgnoreNotifyTracker m_notifyTracker;
};
AZStd::unique_ptr<StaticData> m_data;
};
TEST_F(RCJobTest, CopyCompiledAssets_NoWorkToDo_Succeeds)
{
BuilderParams builderParams;
ProcessJobResponse response;
EXPECT_TRUE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numWarningsAbsorbed, 0);
}
TEST_F(RCJobTest, CopyCompiledAssets_InvalidOutputPath_FailsAndAsserts)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
response.m_outputProducts.push_back({ "file1.txt" }); // make sure that there is at least one product so that it doesn't early out.
// set only the input path, not the output path:
builderParams.m_processJobRequest.m_tempDirPath = m_data->m_absolutePathToTempInputFolder.c_str(); // input working scratch space folder
EXPECT_FALSE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 1);
}
TEST_F(RCJobTest, CopyCompiledAssets_InvalidInputPath_FailsAndAsserts)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
response.m_outputProducts.push_back({ "file1.txt" }); // make sure that there is at least one product so that it doesn't early out.
// set the input dir to be a broken invalid dir:
builderParams.m_processJobRequest.m_tempDirPath = AZ::Uuid::CreateRandom().ToString<AZStd::string>();
builderParams.m_finalOutputDir = QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str()); // output folder in the 'cache'
EXPECT_FALSE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 1);
}
TEST_F(RCJobTest, CopyCompiledAssets_TooLongPath_FailsButDoesNotAssert)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
// set only the output path, but not the input path:
builderParams.m_processJobRequest.m_tempDirPath = m_data->m_absolutePathToTempInputFolder.c_str(); // input working scratch space folder
builderParams.m_finalOutputDir = QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str()); // output folder in the 'cache'
// give it an overly long file name:
AZStd::string reallyLongFileName;
reallyLongFileName.resize(4096, 'x');
response.m_outputProducts.push_back({ reallyLongFileName.c_str() });
EXPECT_FALSE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 1);
}
TEST_F(RCJobTest, CopyCompiledAssets_OutOfDiskSpace_FailsButDoesNotAssert)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
// set only the output path, but not the input path:
builderParams.m_processJobRequest.m_tempDirPath = m_data->m_absolutePathToTempInputFolder.c_str(); // input working scratch space folder
builderParams.m_finalOutputDir = QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str()); // output folder in the 'cache'
response.m_resultCode = ProcessJobResult_Success;
response.m_outputProducts.push_back({ "file1.txt" }); // make sure that there is at least one product so that it doesn't early out.
UnitTestUtils::CreateDummyFile(QDir(m_data->m_absolutePathToTempInputFolder.c_str()).absoluteFilePath("file1.txt"), "output of file 1");
response.m_outputProducts.push_back({ "file2.txt" }); // make sure that there is at least one product so that it doesn't early out.
UnitTestUtils::CreateDummyFile(QDir(m_data->m_absolutePathToTempInputFolder.c_str()).absoluteFilePath("file2.txt"), "output of file 2");
// we exepct exactly one call to check for disk space, (not once for each file), and in this case, we'll return false.
EXPECT_CALL(m_data->m_diskSpaceResponder, CheckSufficientDiskSpace(_,_,_))
.Times(1)
.WillRepeatedly(Return(false));
EXPECT_FALSE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 1);
// no notifies should be hit since the operation should not have been attempted at all (disk space should be checked up front)
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStartPaths.size(), 0);
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStopPaths.size(), 0);
// no cached files should have been copied at all.
QString expectedFinalOutputPath = QDir(QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str())).absoluteFilePath("file1.txt");
EXPECT_FALSE(QFile::exists(expectedFinalOutputPath));
expectedFinalOutputPath = QDir(QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str())).absoluteFilePath("file2.txt");
EXPECT_FALSE(QFile::exists(expectedFinalOutputPath));
}
// The RC Copy Compiled Assets routine is supposed to check up front for problem situations such as out of disk space
// or missing source files, before it tries to perform any operation. This test gives it one file which does work
// but one missing file also, and expects it to fail (without asserting) but without even trying to copy the files at all.
TEST_F(RCJobTest, CopyCompiledAssets_MissingInputFile_Fails_DoesNotAssert_DoesNotAlterCache)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
// set only the output path, but not the input path:
builderParams.m_processJobRequest.m_tempDirPath = m_data->m_absolutePathToTempInputFolder.c_str(); // input working scratch space folder
builderParams.m_finalOutputDir = QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str()); // output folder in the 'cache'
response.m_resultCode = ProcessJobResult_Success;
response.m_outputProducts.push_back({ "FiLe1.TxT" }); // make sure that there is at least one product so that it doesn't early out.
UnitTestUtils::CreateDummyFile(QDir(m_data->m_absolutePathToTempInputFolder.c_str()).absoluteFilePath("FiLe1.TxT"), "output of file 1");
response.m_outputProducts.push_back({ "FiLe2.txt" });
// note well that we create the first file but we don't acutally create the second one, so it is missing.
EXPECT_FALSE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 1);
// no notifies should be hit since the operation should not have been attempted at all.
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStartPaths.size(), 0);
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStopPaths.size(), 0);
QString expectedFinalOutputPath = QDir(QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str())).absoluteFilePath("file1.txt");
EXPECT_FALSE(QFile::exists(expectedFinalOutputPath));
}
TEST_F(RCJobTest, CopyCompiledAssets_AbsolutePath_SucceedsAndNotifiesAboutCacheDelete)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
// set only the output path, but not the input path:
builderParams.m_processJobRequest.m_tempDirPath = m_data->m_absolutePathToTempInputFolder.c_str(); // input working scratch space folder
builderParams.m_finalOutputDir = QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str()); // output folder in the 'cache'
response.m_resultCode = ProcessJobResult_Success;
// make up a completely different random path to put an absolute file in:
QTemporaryDir extraDir;
QDir randomDir(extraDir.path());
randomDir.mkpath(extraDir.path());
QString absolutePathToCreate = randomDir.absoluteFilePath("someabsolutefile.txt");
UnitTestUtils::CreateDummyFile(absolutePathToCreate, "output of the file");
response.m_outputProducts.push_back({ absolutePathToCreate.toUtf8().constData() }); // absolute path to file not actually in the product scratch space folder.
// this should copy that file into the target path.
EXPECT_TRUE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStartPaths.size(), 1);
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStopPaths.size(), 1);
// note that output files are automatically lowercased within the cache but the path to the cache folder itself is not lowered, just the output file.
// this is to make sure that game code never has to worry about the casing of output file paths, CRYPAK can just always lower the relpath and always know
// that even on case-sensitive platforms it won't cause trouble or a difference of behavior from non-case-sensitive ones.
QString expectedFinalOutputPath = QDir(QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str())).absoluteFilePath("someabsolutefile.txt");
ASSERT_STREQ(m_data->m_notifyTracker.m_capturedStartPaths[0].c_str(), m_data->m_notifyTracker.m_capturedStopPaths[0].c_str());
ASSERT_STREQ(m_data->m_notifyTracker.m_capturedStartPaths[0].c_str(), expectedFinalOutputPath.toUtf8().constData());
EXPECT_TRUE(QFile::exists(expectedFinalOutputPath));
}
TEST_F(RCJobTest, CopyCompiledAssets_RelativePath_SucceedsAndNotifiesAboutCacheDelete)
{
BuilderParams builderParams;
ProcessJobResponse response;
response.m_resultCode = ProcessJobResult_Success;
// set only the output path, but not the input path:
builderParams.m_processJobRequest.m_tempDirPath = m_data->m_absolutePathToTempInputFolder.c_str(); // input working scratch space folder
builderParams.m_finalOutputDir = QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str()); // output folder in the 'cache'
response.m_resultCode = ProcessJobResult_Success;
response.m_outputProducts.push_back({ "FiLe1.TxT" }); // make sure that there is at least one product so that it doesn't early out.
UnitTestUtils::CreateDummyFile(QDir(m_data->m_absolutePathToTempInputFolder.c_str()).absoluteFilePath("FiLe1.TxT"), "output of file 1");
EXPECT_TRUE(RCJob::CopyCompiledAssets(builderParams, response));
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStartPaths.size(), 1);
ASSERT_EQ(m_data->m_notifyTracker.m_capturedStopPaths.size(), 1);
// note that output files are automatically lowercased within the cache but the path to the cache folder itself is not lowered, just the output file.
// this is to make sure that game code never has to worry about the casing of output file paths, CRYPAK can just always lower the relpath and always know
// that even on case-sensitive platforms it won't cause trouble or a difference of behavior from non-case-sensitive ones.
QString expectedFinalOutputPath = QDir(QString::fromUtf8(m_data->m_absolutePathToTempOutputFolder.c_str())).absoluteFilePath("file1.txt");
ASSERT_STREQ(m_data->m_notifyTracker.m_capturedStartPaths[0].c_str(), m_data->m_notifyTracker.m_capturedStopPaths[0].c_str());
ASSERT_STREQ(m_data->m_notifyTracker.m_capturedStartPaths[0].c_str(), expectedFinalOutputPath.toUtf8().constData());
EXPECT_TRUE(QFile::exists(expectedFinalOutputPath));
// Start and end paths should, however, be normalized even if the input is not.
QString normalizedStartPath = QString::fromUtf8(m_data->m_notifyTracker.m_capturedStartPaths[0].c_str());
normalizedStartPath = AssetUtilities::NormalizeFilePath(normalizedStartPath);
EXPECT_STREQ(normalizedStartPath.toUtf8().constData(), m_data->m_notifyTracker.m_capturedStartPaths[0].c_str());
QString normalizedStopPath = QString::fromUtf8(m_data->m_notifyTracker.m_capturedStopPaths[0].c_str());
normalizedStopPath = AssetUtilities::NormalizeFilePath(normalizedStopPath);
EXPECT_STREQ(normalizedStopPath.toUtf8().constData(), m_data->m_notifyTracker.m_capturedStopPaths[0].c_str());
}
} // end namespace UnitTests
@@ -0,0 +1,17 @@
/*
* 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 <AzTest/AzTest.h>
@@ -0,0 +1,81 @@
/*
* 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 "utilities/BatchApplicationManager.h"
#include <AzTest/AzTest.h>
#include <AzTest/Utils.h>
#include <native/tests/BaseAssetProcessorTest.h>
DECLARE_AZ_UNIT_TEST_MAIN()
int RunUnitTests(int argc, char* argv[], bool& ranUnitTests)
{
ranUnitTests = true;
INVOKE_AZ_UNIT_TEST_MAIN(nullptr); // nullptr turns off default test environment used to catch stray asserts
// This looks a bit weird, but the macro returns conditionally, so *if* we get here, it means the unit tests didn't run
ranUnitTests = false;
return 0;
}
int main(int argc, char* argv[])
{
qputenv("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM", "1");
// If "--unittest" is present on the command line, run unit testing
// and return immediately. Otherwise, continue as normal.
AZ::Test::addTestEnvironment(new BaseAssetProcessorTestEnvironment());
bool pauseOnComplete = false;
if (AZ::Test::ContainsParameter(argc, argv, "--pause-on-completion"))
{
pauseOnComplete = true;
}
bool ranUnitTests;
int result = RunUnitTests(argc, argv, ranUnitTests);
if (ranUnitTests)
{
if (pauseOnComplete)
{
system("pause");
}
return result;
}
BatchApplicationManager applicationManager(&argc, &argv);
setvbuf(stdout, NULL, _IONBF, 0); // Disabling output buffering to fix test failures due to incomplete logs
ApplicationManager::BeforeRunStatus status = applicationManager.BeforeRun();
if (status != ApplicationManager::BeforeRunStatus::Status_Success)
{
if (status == ApplicationManager::BeforeRunStatus::Status_Restarting)
{
//AssetProcessor will restart
return 0;
}
else
{
//Initialization failed
return 1;
}
}
return applicationManager.Run() ? 0 : 1;
}
@@ -0,0 +1,247 @@
/*
* 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 <native/tests/utilities/JobModelTest.h>
TEST_F(JobModelUnitTests, Test_RemoveMiddleJob)
{
VerifyModel(); // verify up front for sanity.
AzToolsFramework::AssetSystem::JobInfo jobInfo;
jobInfo.m_sourceFile = "source2.txt";
jobInfo.m_platform = "platform";
jobInfo.m_jobKey = "jobKey";
AssetProcessor::QueueElementID elementId("source2.txt", "platform", "jobKey");
auto iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
unsigned int jobIndex = iter.value();
ASSERT_EQ(jobIndex, 1); // second job
ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), 6);
m_unitTestJobModel->OnJobRemoved(jobInfo);
ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), 5);
iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
ASSERT_EQ(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
AssetProcessor::CachedJobInfo* cachedJobInfo = m_unitTestJobModel->m_cachedJobs[jobIndex];
ASSERT_EQ(cachedJobInfo->m_elementId.GetInputAssetName(), QString("source3.txt"));
// Checking index of last job
elementId.SetInputAssetName("source6.txt");
iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
jobIndex = iter.value();
ASSERT_EQ(jobIndex, 4);
VerifyModel();
}
TEST_F(JobModelUnitTests, Test_RemoveFirstJob)
{
VerifyModel(); // verify up front for sanity.
AzToolsFramework::AssetSystem::JobInfo jobInfo;
jobInfo.m_sourceFile = "source1.txt";
jobInfo.m_platform = "platform";
jobInfo.m_jobKey = "jobKey";
AssetProcessor::QueueElementID elementId("source1.txt", "platform", "jobKey");
auto iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
unsigned int jobIndex = iter.value();
ASSERT_EQ(jobIndex, 0); //first job
ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), 6);
m_unitTestJobModel->OnJobRemoved(jobInfo);
ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), 5);
iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
ASSERT_EQ(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
AssetProcessor::CachedJobInfo* cachedJobInfo = m_unitTestJobModel->m_cachedJobs[jobIndex];
ASSERT_EQ(cachedJobInfo->m_elementId.GetInputAssetName(), QString("source2.txt"));
// Checking index of last job
elementId.SetInputAssetName("source6.txt");
iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
jobIndex = iter.value();
ASSERT_EQ(jobIndex, 4);
VerifyModel();
}
TEST_F(JobModelUnitTests, Test_RemoveLastJob)
{
VerifyModel(); // verify up front for sanity.
AzToolsFramework::AssetSystem::JobInfo jobInfo;
jobInfo.m_sourceFile = "source6.txt";
jobInfo.m_platform = "platform";
jobInfo.m_jobKey = "jobKey";
AssetProcessor::QueueElementID elementId("source6.txt", "platform", "jobKey");
auto iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
unsigned int jobIndex = iter.value();
ASSERT_EQ(jobIndex, 5); //last job
ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), 6);
m_unitTestJobModel->OnJobRemoved(jobInfo);
ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), 5);
iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
ASSERT_EQ(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
AssetProcessor::CachedJobInfo* cachedJobInfo = m_unitTestJobModel->m_cachedJobs[jobIndex - 1];
ASSERT_EQ(cachedJobInfo->m_elementId.GetInputAssetName(), QString("source5.txt"));
// Checking index of first job
elementId.SetInputAssetName("source1.txt");
iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
jobIndex = iter.value();
ASSERT_EQ(jobIndex, 0);
VerifyModel();
}
TEST_F(JobModelUnitTests, Test_RemoveAllJobsBySource)
{
VerifyModel(); // verify up front for sanity.
AssetProcessor::CachedJobInfo* jobInfo1 = new AssetProcessor::CachedJobInfo();
jobInfo1->m_elementId.SetInputAssetName("source3.txt"); // this is the second job for this source - the fixture creates one
jobInfo1->m_elementId.SetPlatform("platform_2"); // differing job keys
jobInfo1->m_elementId.SetJobDescriptor("jobKey_3"); // differing descriptor
jobInfo1->m_jobState = AzToolsFramework::AssetSystem::JobStatus::Completed;
m_unitTestJobModel->m_cachedJobs.push_back(jobInfo1);
m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo1->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
AssetProcessor::QueueElementID elementId("source3.txt", "platform_2", "jobKey_3");
auto iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
unsigned int jobIndex = iter.value();
ASSERT_EQ(jobIndex, 6); //last job
ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), 7);
m_unitTestJobModel->OnSourceRemoved("source3.txt");
// both sources should be removed!
ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), 5);
VerifyModel();
// make sure source3 is completely gone.
for (int idx = 0; idx < m_unitTestJobModel->m_cachedJobs.size(); idx++)
{
AssetProcessor::CachedJobInfo* jobInfo = m_unitTestJobModel->m_cachedJobs[idx];
ASSERT_NE(jobInfo->m_elementId.GetInputAssetName(), QString::fromUtf8("source3.txt"));
}
}
TEST_F(JobModelUnitTests, Test_RemoveAllJobsBySourceFolder)
{
VerifyModel(); // verify up front for sanity.
AssetProcessor::CachedJobInfo* testJobInfo = new AssetProcessor::CachedJobInfo();
testJobInfo->m_elementId.SetInputAssetName("sourceFolder1/source.txt");
testJobInfo->m_elementId.SetPlatform("platform");
testJobInfo->m_elementId.SetJobDescriptor("jobKey");
testJobInfo->m_jobState = AzToolsFramework::AssetSystem::JobStatus::Completed;
m_unitTestJobModel->m_cachedJobs.push_back(testJobInfo);
m_unitTestJobModel->m_cachedJobsLookup.insert(testJobInfo->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
AssetProcessor::QueueElementID elementId("sourceFolder1/source.txt", "platform", "jobKey");
auto iter = m_unitTestJobModel->m_cachedJobsLookup.find(elementId);
ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
unsigned int jobIndex = iter.value();
ASSERT_EQ(jobIndex, 6); //last job
ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), 7);
m_unitTestJobModel->OnFolderRemoved("sourceFolder1");
ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), 6);
VerifyModel();
// make sure sourceFolder1/source.txt is completely gone.
for (int idx = 0; idx < m_unitTestJobModel->m_cachedJobs.size(); idx++)
{
AssetProcessor::CachedJobInfo* jobInfo = m_unitTestJobModel->m_cachedJobs[idx];
ASSERT_NE(jobInfo->m_elementId.GetInputAssetName(), QString::fromUtf8("sourceFolder1/source.txt"));
}
}
void JobModelUnitTests::SetUp()
{
AssetProcessorTest::SetUp();
m_unitTestJobModel = new UnitTestJobModel();
AssetProcessor::CachedJobInfo* jobInfo1 = new AssetProcessor::CachedJobInfo();
jobInfo1->m_elementId.SetInputAssetName("source1.txt");
jobInfo1->m_elementId.SetPlatform("platform");
jobInfo1->m_elementId.SetJobDescriptor("jobKey");
jobInfo1->m_jobState = AzToolsFramework::AssetSystem::JobStatus::Completed;
m_unitTestJobModel->m_cachedJobs.push_back(jobInfo1);
m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo1->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
AssetProcessor::CachedJobInfo* jobInfo2 = new AssetProcessor::CachedJobInfo();
jobInfo2->m_elementId.SetInputAssetName("source2.txt");
jobInfo2->m_elementId.SetPlatform("platform");
jobInfo2->m_elementId.SetJobDescriptor("jobKey");
m_unitTestJobModel->m_cachedJobs.push_back(jobInfo2);
m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo2->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
AssetProcessor::CachedJobInfo* jobInfo3 = new AssetProcessor::CachedJobInfo();
jobInfo3->m_elementId.SetInputAssetName("source3.txt");
jobInfo3->m_elementId.SetPlatform("platform");
jobInfo3->m_elementId.SetJobDescriptor("jobKey");
m_unitTestJobModel->m_cachedJobs.push_back(jobInfo3);
m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo3->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
AssetProcessor::CachedJobInfo* jobInfo4 = new AssetProcessor::CachedJobInfo();
jobInfo4->m_elementId.SetInputAssetName("source4.txt");
jobInfo4->m_elementId.SetPlatform("platform");
jobInfo4->m_elementId.SetJobDescriptor("jobKey");
m_unitTestJobModel->m_cachedJobs.push_back(jobInfo4);
m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo4->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
AssetProcessor::CachedJobInfo* jobInfo5 = new AssetProcessor::CachedJobInfo();
jobInfo5->m_elementId.SetInputAssetName("source5.txt");
jobInfo5->m_elementId.SetPlatform("platform");
jobInfo5->m_elementId.SetJobDescriptor("jobKey");
m_unitTestJobModel->m_cachedJobs.push_back(jobInfo5);
m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo5->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
AssetProcessor::CachedJobInfo* jobInfo6 = new AssetProcessor::CachedJobInfo();
jobInfo6->m_elementId.SetInputAssetName("source6.txt");
jobInfo6->m_elementId.SetPlatform("platform");
jobInfo6->m_elementId.SetJobDescriptor("jobKey");
m_unitTestJobModel->m_cachedJobs.push_back(jobInfo6);
m_unitTestJobModel->m_cachedJobsLookup.insert(jobInfo6->m_elementId, aznumeric_caster(m_unitTestJobModel->m_cachedJobs.size() - 1));
}
void JobModelUnitTests::TearDown()
{
delete m_unitTestJobModel;
AssetProcessorTest::TearDown();
}
void JobModelUnitTests::VerifyModel()
{
// Every job should exist in the lookup map as well.
ASSERT_EQ(m_unitTestJobModel->m_cachedJobs.size(), m_unitTestJobModel->m_cachedJobsLookup.size());
// every job in the vector should have a corresponding element in the lookup table.
for (int idx = 0; idx < m_unitTestJobModel->m_cachedJobs.size(); idx++)
{
AssetProcessor::CachedJobInfo* jobInfo = m_unitTestJobModel->m_cachedJobs[idx];
auto iter = m_unitTestJobModel->m_cachedJobsLookup.find(jobInfo->m_elementId);
ASSERT_NE(iter, m_unitTestJobModel->m_cachedJobsLookup.end());
}
// this tests the other direction - every job in the lookup table should map to a job in the vector
// we also verify that its the appropriate job and not an off-by-one type of problem.
for (const auto& key : m_unitTestJobModel->m_cachedJobsLookup.keys())
{
int expectedIndex = m_unitTestJobModel->m_cachedJobsLookup[key];
ASSERT_LT(expectedIndex, m_unitTestJobModel->m_cachedJobs.size());
ASSERT_EQ(m_unitTestJobModel->m_cachedJobs[expectedIndex]->m_elementId, key);
}
}
@@ -0,0 +1,43 @@
/*
* 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 <native/resourcecompiler/JobsModel.h>
#include <native/tests/AssetProcessorTest.h>
#include <QCoreApplication>
class UnitTestJobModel
: public AssetProcessor::JobsModel
{
public:
~UnitTestJobModel() override = default;
friend class GTEST_TEST_CLASS_NAME_(JobModelUnitTests, Test_RemoveMiddleJob);
friend class GTEST_TEST_CLASS_NAME_(JobModelUnitTests, Test_RemoveFirstJob);
friend class GTEST_TEST_CLASS_NAME_(JobModelUnitTests, Test_RemoveLastJob);
friend class GTEST_TEST_CLASS_NAME_(JobModelUnitTests, Test_RemoveAllJobsBySource);
friend class GTEST_TEST_CLASS_NAME_(JobModelUnitTests, Test_RemoveAllJobsBySourceFolder);
friend class JobModelUnitTests;
};
class JobModelUnitTests
: public AssetProcessor::AssetProcessorTest
{
public:
void SetUp() override;
void TearDown() override;
void VerifyModel();
UnitTestJobModel* m_unitTestJobModel;
};
@@ -0,0 +1,613 @@
/*
* 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 <QHash>
#include "native/tests/AssetProcessorTest.h"
#include <AzCore/std/parallel/thread.h>
using namespace AssetUtilities;
class AssetUtilitiesTest
: public AssetProcessor::AssetProcessorTest
{
void SetUp() override
{
AssetProcessorTest::SetUp();
if (AZ::IO::FileIOBase::GetInstance() == nullptr)
{
m_localFileIo = aznew AZ::IO::LocalFileIO();
AZ::IO::FileIOBase::SetInstance(m_localFileIo);
}
}
void TearDown() override
{
if (m_localFileIo)
{
delete m_localFileIo;
m_localFileIo = nullptr;
AZ::IO::FileIOBase::SetInstance(nullptr);
}
AssetProcessorTest::TearDown();
}
AZ::IO::FileIOBase* m_localFileIo{};
};
TEST_F(AssetUtilitiesTest, NormlizeFilePath_NormalizedValidPathRelPath_Valid)
{
QString result = NormalizeFilePath("a/b\\c\\d/E.txt");
EXPECT_STREQ(result.toUtf8().constData(), "a/b/c/d/E.txt");
}
TEST_F(AssetUtilitiesTest, NormlizeFilePath_NormalizedValidPathFullPath_Valid)
{
QString result = NormalizeFilePath("c:\\a/b\\c\\d/E.txt");
// on windows, drive letters are normalized to full
#if defined(AZ_PLATFORM_WINDOWS)
ASSERT_TRUE(result.compare("C:/a/b/c/d/E.txt", Qt::CaseSensitive) == 0);
#else
// on other platforms, C: is a relative path to a file called 'c:')
EXPECT_STREQ(result.toUtf8().constData(), "c:/a/b/c/d/E.txt");
#endif
}
TEST_F(AssetUtilitiesTest, NormlizeFilePath_NormalizedValidDirRelPath_Valid)
{
QString result = NormalizeDirectoryPath("a/b\\c\\D");
EXPECT_STREQ(result.toUtf8().constData(), "a/b/c/D");
}
TEST_F(AssetUtilitiesTest, NormlizeFilePath_NormalizedValidDirFullPath_Valid)
{
QString result = NormalizeDirectoryPath("c:\\a/b\\C\\d\\");
// on windows, drive letters are normalized to full
#if defined(AZ_PLATFORM_WINDOWS)
EXPECT_STREQ(result.toUtf8().constData(), "C:/a/b/C/d");
#else
EXPECT_STREQ(result.toUtf8().constData(), "c:/a/b/C/d");
#endif
}
TEST_F(AssetUtilitiesTest, ComputeCRC32Lowercase_IsCaseInsensitive)
{
const char* upperCaseString = "HELLOworld";
const char* lowerCaseString = "helloworld";
EXPECT_EQ(AssetUtilities::ComputeCRC32Lowercase(lowerCaseString), AssetUtilities::ComputeCRC32Lowercase(upperCaseString));
// also try the length-based one.
EXPECT_EQ(AssetUtilities::ComputeCRC32Lowercase(lowerCaseString, size_t(5)), AssetUtilities::ComputeCRC32Lowercase(upperCaseString, size_t(5)));
}
TEST_F(AssetUtilitiesTest, ComputeCRC32_IsCaseSensitive)
{
const char* upperCaseString = "HELLOworld";
const char* lowerCaseString = "helloworld";
EXPECT_NE(AssetUtilities::ComputeCRC32(lowerCaseString), AssetUtilities::ComputeCRC32(upperCaseString));
// also try the length-based one.
EXPECT_NE(AssetUtilities::ComputeCRC32(lowerCaseString, size_t(5)), AssetUtilities::ComputeCRC32(upperCaseString, size_t(5)));
}
TEST_F(AssetUtilitiesTest, UpdateToCorrectCase_MissingFile_ReturnsFalse)
{
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
QString fileName = "someFile.txt";
EXPECT_FALSE(AssetUtilities::UpdateToCorrectCase(canonicalTempDirPath, fileName));
}
TEST_F(AssetUtilitiesTest, UpdateToCorrectCase_ExistingFile_ReturnsTrue_CorrectsCase)
{
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
QStringList thingsToTry;
thingsToTry << "SomeFile.TxT";
thingsToTry << "otherfile.txt";
thingsToTry << "subfolder1/otherfile.txt";
thingsToTry << "subfolder2\\otherfile.txt";
thingsToTry << "subFolder3\\somefile.txt";
thingsToTry << "subFolder4\\subfolder6\\somefile.txt";
thingsToTry << "subFolder5\\subfolder7/someFile.txt";
thingsToTry << "specialFileName[.txt";
thingsToTry << "specialFileName].txt";
thingsToTry << "specialFileName!.txt";
thingsToTry << "specialFileName#.txt";
thingsToTry << "specialFileName$.txt";
thingsToTry << "specialFile%Name%.txt";
thingsToTry << "specialFileName&.txt";
thingsToTry << "specialFileName(.txt";
thingsToTry << "specialFileName+.txt";
thingsToTry << "specialFileName[9].txt";
thingsToTry << "specialFileName[A-Za-z].txt"; // these should all be treated as literally the name of the file, not a regex!
for (QString triedThing : thingsToTry)
{
triedThing = NormalizeFilePath(triedThing);
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath(triedThing)));
QString lowercaseVersion = triedThing.toLower();
// each one should be found. If it fails, we'll pipe out the name of the file it fails on for extra context.
EXPECT_TRUE(AssetUtilities::UpdateToCorrectCase(canonicalTempDirPath, lowercaseVersion)) << "File being Examined: " << lowercaseVersion.toUtf8().constData();
// each one should correct, and return a normalized path.
EXPECT_STREQ(AssetUtilities::NormalizeFilePath(lowercaseVersion).toUtf8().constData(), AssetUtilities::NormalizeFilePath(triedThing).toUtf8().constData());
}
}
TEST_F(AssetUtilitiesTest, GenerateFingerprint_BasicTest)
{
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
QString absoluteTestFilePath1 = tempPath.absoluteFilePath("basicfile.txt");
QString absoluteTestFilePath2 = tempPath.absoluteFilePath("basicfile2.txt");
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath1, "contents"));
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath2, "contents"));
AZStd::string fileEncoded1 = absoluteTestFilePath1.toUtf8().constData();
AZStd::string fileEncoded2 = absoluteTestFilePath2.toUtf8().constData();
AssetProcessor::JobDetails jobDetail;
// it is expected that the only parts of jobDetails that matter are:
// jobDetail.m_extraInformationForFingerprinting
// jobDetail.m_fingerprintFiles
// jobDetail.m_jobDependencyList
jobDetail.m_extraInformationForFingerprinting = "extra info1";
// the fingerprint should always be stable over repeated runs, even with minimal info:
unsigned int result1 = AssetUtilities::GenerateFingerprint(jobDetail);
unsigned int result2 = AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_EQ(result1, result2);
// the fingerprint should always be different when anything changes:
jobDetail.m_extraInformationForFingerprinting = "extra info1";
result1 = AssetUtilities::GenerateFingerprint(jobDetail);
jobDetail.m_extraInformationForFingerprinting = "extra info2";
result2 = AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_NE(result1, result2);
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(absoluteTestFilePath1.toUtf8().constData(), "basicfile.txt"));
result1 = AssetUtilities::GenerateFingerprint(jobDetail);
result2 = AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_EQ(result1, result2);
// mutating the dependency list should mutate the fingerprint, even if the extra info doesn't change.
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(absoluteTestFilePath2.toUtf8().constData(), "basicfile2.txt"));
result2 = AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_NE(result1, result2);
UnitTestUtils::SleepForMinimumFileSystemTime();
// mutating the actual files should mutate the fingerprint, even if the file list doesn't change.
// note that both files are in the file list, so changing just the one should result in a change in hash:
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath1, "contents new"));
result1 = AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_NE(result1, result2);
// changing the other should also change the hash:
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath2, "contents new2"));
result2 = AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_NE(result1, result2);
}
TEST_F(AssetUtilitiesTest, GenerateFingerprint_Empty_Asserts)
{
AssetProcessor::JobDetails jobDetail;
AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 1);
m_errorAbsorber->Clear();
}
TEST_F(AssetUtilitiesTest, GenerateFingerprint_MissingFile_NotSameAsZeroByteFile)
{
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
QString absoluteTestFilePath1 = tempPath.absoluteFilePath("basicfile.txt");
QString absoluteTestFilePath2 = tempPath.absoluteFilePath("basicfile2.txt");
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath1, "")); // empty file
// note: basicfile1 exists but is empty, whereas basicfile2, 3 are missing entirely.
AssetProcessor::JobDetails jobDetail;
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile.txt").toUtf8().constData(), "basicfile.txt"));
AZ::u32 fingerprint1 = AssetUtilities::GenerateFingerprint(jobDetail);
jobDetail.m_fingerprintFiles.clear();
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile2.txt").toUtf8().constData(), "basicfile2.txt"));
AZ::u32 fingerprint2 = AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_NE(fingerprint1, fingerprint2);
}
TEST_F(AssetUtilitiesTest, GenerateFingerprint_MissingFile_NotSameAsOtherMissingFile)
{
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
QString absoluteTestFilePath1 = tempPath.absoluteFilePath("basicfile.txt");
QString absoluteTestFilePath2 = tempPath.absoluteFilePath("basicfile2.txt");
// we create no files on disk.
AssetProcessor::JobDetails jobDetail;
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile.txt").toUtf8().constData(), "basicfile.txt"));
AZ::u32 fingerprint1 = AssetUtilities::GenerateFingerprint(jobDetail);
jobDetail.m_fingerprintFiles.clear();
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile2.txt").toUtf8().constData(), "basicfile2.txt"));
AZ::u32 fingerprint2 = AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_NE(fingerprint1, fingerprint2);
}
TEST_F(AssetUtilitiesTest, GenerateFingerprint_OneFile_Differs)
{
// this test makes sure that changing each part of jobDetail relevant to fingerprints causes the resulting fingerprint to change.
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
QString absoluteTestFilePath1 = tempPath.absoluteFilePath("basicfile.txt");
QString absoluteTestFilePath2 = tempPath.absoluteFilePath("basicfile2.txt");
QString absoluteTestFilePath3 = tempPath.absoluteFilePath("basicfile3.txt");
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath1, "contents"));
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath2, "contents")); // same contents
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath3, "contents2")); // different contents
AssetProcessor::JobDetails jobDetail;
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile.txt").toUtf8().constData(), "basicfile.txt"));
AZ::u32 fingerprint1 = AssetUtilities::GenerateFingerprint(jobDetail);
jobDetail.m_fingerprintFiles.clear();
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile2.txt").toUtf8().constData(), "basicfile2.txt"));
AZ::u32 fingerprint2 = AssetUtilities::GenerateFingerprint(jobDetail);
jobDetail.m_fingerprintFiles.clear();
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile3.txt").toUtf8().constData(), "basicfile3.txt"));
AZ::u32 fingerprint3 = AssetUtilities::GenerateFingerprint(jobDetail);
jobDetail.m_fingerprintFiles.clear();
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile.txt").toUtf8().constData(), "basicfile.txt"));
EXPECT_EQ(AssetUtilities::GenerateFingerprint(jobDetail), fingerprint1);
jobDetail.m_fingerprintFiles.clear();
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile2.txt").toUtf8().constData(), "basicfile2.txt"));
EXPECT_EQ(AssetUtilities::GenerateFingerprint(jobDetail), fingerprint2);
jobDetail.m_fingerprintFiles.clear();
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile3.txt").toUtf8().constData(), "basicfile3.txt"));
EXPECT_EQ(AssetUtilities::GenerateFingerprint(jobDetail), fingerprint3);
EXPECT_NE(fingerprint1, fingerprint2);
EXPECT_NE(fingerprint2, fingerprint3);
EXPECT_NE(fingerprint3, fingerprint1);
}
TEST_F(AssetUtilitiesTest, GenerateFingerprint_MultipleFile_Differs)
{
// given multiple files, make sure that the fingerprint for multiple files differs from the one file (that each file is taken into account)
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
QString absoluteTestFilePath1 = tempPath.absoluteFilePath("basicfile.txt");
QString absoluteTestFilePath2 = tempPath.absoluteFilePath("basicfile2.txt");
QString absoluteTestFilePath3 = tempPath.absoluteFilePath("basicfile3.txt");
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath1, "contents"));
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath2, "contents")); // same contents
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath3, "contents2")); // different contents
AssetProcessor::JobDetails jobDetail;
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile.txt").toUtf8().constData(), "basicfile.txt"));
AZ::u32 fingerprint1 = AssetUtilities::GenerateFingerprint(jobDetail);
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile2.txt").toUtf8().constData(), "basicfile2.txt"));
AZ::u32 fingerprint2 = AssetUtilities::GenerateFingerprint(jobDetail);
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile3.txt").toUtf8().constData(), "basicfile3.txt"));
AZ::u32 fingerprint3 = AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_NE(fingerprint1, fingerprint2);
EXPECT_NE(fingerprint2, fingerprint3);
EXPECT_NE(fingerprint3, fingerprint1);
jobDetail.m_fingerprintFiles.clear();
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile.txt").toUtf8().constData(), "basicfile.txt"));
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile2.txt").toUtf8().constData(), "basicfile2.txt"));
fingerprint1 = AssetUtilities::GenerateFingerprint(jobDetail);
jobDetail.m_fingerprintFiles.clear();
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile2.txt").toUtf8().constData(), "basicfile2.txt"));
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile3.txt").toUtf8().constData(), "basicfile3.txt"));
fingerprint2 = AssetUtilities::GenerateFingerprint(jobDetail);
jobDetail.m_fingerprintFiles.clear();
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile1.txt").toUtf8().constData(), "basicfile1.txt"));
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile3.txt").toUtf8().constData(), "basicfile3.txt"));
fingerprint3 = AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_NE(fingerprint1, fingerprint2);
EXPECT_NE(fingerprint2, fingerprint3);
EXPECT_NE(fingerprint3, fingerprint1);
}
TEST_F(AssetUtilitiesTest, GenerateFingerprint_OrderOnceJobDependency_NoChange)
{
// OrderOnce Job dependency should not alter the fingerprint of the job
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
const char relFile1Path[] = "file.txt";
const char relFile2Path[] = "secondFile.txt";
QString absoluteTestFile1Path = tempPath.absoluteFilePath(relFile1Path);
QString absoluteTestFile2Path = tempPath.absoluteFilePath(relFile2Path);
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFile1Path, "contents"));
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFile2Path, "contents"));
AssetProcessor::JobDetails jobDetail;
jobDetail.m_jobEntry.m_databaseSourceName = relFile1Path;
jobDetail.m_jobEntry.m_watchFolderPath = tempPath.absolutePath();
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(absoluteTestFile1Path.toUtf8().constData(), relFile1Path));
AZ::u32 fingerprintWithoutOrderOnceJobDependency = AssetUtilities::GenerateFingerprint(jobDetail);
AssetBuilderSDK::SourceFileDependency dep = { relFile2Path, AZ::Uuid::CreateNull() };
AssetBuilderSDK::JobDependency jobDep("key", "pc", AssetBuilderSDK::JobDependencyType::OrderOnce, dep);
jobDetail.m_jobDependencyList.push_back(AssetProcessor::JobDependencyInternal(jobDep));
AZ::u32 fingerprintWithOrderOnceJobDependency = AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_EQ(fingerprintWithoutOrderOnceJobDependency, fingerprintWithOrderOnceJobDependency);
}
namespace AssetUtilsTest
{
class MockJobDependencyResponder : public AssetProcessor::ProcessingJobInfoBus::Handler
{
public:
MOCK_METHOD1(GetJobFingerprint, AZ::u32(const AssetProcessor::JobIndentifier&));
};
}
TEST_F(AssetUtilitiesTest, GenerateFingerprint_GivenJobDependencies_AffectsOutcome)
{
using namespace testing;
using ::testing::NiceMock;
NiceMock<AssetUtilsTest::MockJobDependencyResponder> responder;
responder.BusConnect();
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
QString absoluteTestFilePath1 = tempPath.absoluteFilePath("basicfile.txt");
AssetProcessor::JobDetails jobDetail;
jobDetail.m_fingerprintFiles.insert(AZStd::make_pair(tempPath.absoluteFilePath("basicfile.txt").toUtf8().constData(), "basicfile.txt"));
AZ::u32 fingerprint1 = AssetUtilities::GenerateFingerprint(jobDetail);
// add a job dependency - it should alter the fingerprint, even if the file does not exist.
AssetBuilderSDK::JobDependency jobDep("thing", "pc", AssetBuilderSDK::JobDependencyType::Order, AssetBuilderSDK::SourceFileDependency("basicfile2.txt", AZ::Uuid::CreateNull()));
AssetProcessor::JobDependencyInternal internalJobDep(jobDep);
internalJobDep.m_builderUuidList.insert(AZ::Uuid::CreateRandom());
jobDetail.m_jobDependencyList.push_back(internalJobDep);
EXPECT_CALL(responder, GetJobFingerprint(_))
.WillOnce(
Return(0x12341234));
AZ::u32 fingerprint2 = AssetUtilities::GenerateFingerprint(jobDetail);
// different job fingerprint -> different result
EXPECT_CALL(responder, GetJobFingerprint(_))
.WillOnce(
Return(0x11111111));
AZ::u32 fingerprint3 = AssetUtilities::GenerateFingerprint(jobDetail);
EXPECT_NE(fingerprint1, fingerprint2);
EXPECT_NE(fingerprint2, fingerprint3);
EXPECT_NE(fingerprint3, fingerprint1);
}
TEST_F(AssetUtilitiesTest, GetFileFingerprint_BasicTest)
{
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
QString absoluteTestFilePath1 = tempPath.absoluteFilePath("basicfile.txt");
QString absoluteTestFilePath2 = tempPath.absoluteFilePath("basicfile2.txt");
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath1, "contents"));
UnitTestUtils::SleepForMinimumFileSystemTime();
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath2, "contents2"));
AZStd::string fileEncoded1 = absoluteTestFilePath1.toUtf8().constData();
AZStd::string fileEncoded2 = absoluteTestFilePath2.toUtf8().constData();
// repeatedly hashing the same file should result in the same hash:
EXPECT_STREQ(AssetUtilities::GetFileFingerprint(fileEncoded1, "").c_str(), AssetUtilities::GetFileFingerprint(fileEncoded1, "").c_str());
EXPECT_STREQ(AssetUtilities::GetFileFingerprint(fileEncoded1, "Name").c_str(), AssetUtilities::GetFileFingerprint(fileEncoded1, "Name").c_str());
EXPECT_STREQ(AssetUtilities::GetFileFingerprint(fileEncoded2, "").c_str(), AssetUtilities::GetFileFingerprint(fileEncoded2, "").c_str());
EXPECT_STREQ(AssetUtilities::GetFileFingerprint(fileEncoded2, "Name").c_str(), AssetUtilities::GetFileFingerprint(fileEncoded2, "Name").c_str());
// mutating the 'name' should mutate the fingerprint:
EXPECT_STRNE(AssetUtilities::GetFileFingerprint(fileEncoded1, "").c_str(), AssetUtilities::GetFileFingerprint(fileEncoded1, "Name").c_str());
// two different files should not hash to the same fingerprint:
EXPECT_STRNE(AssetUtilities::GetFileFingerprint(fileEncoded1, "").c_str(), AssetUtilities::GetFileFingerprint(fileEncoded2, "").c_str());
UnitTestUtils::SleepForMinimumFileSystemTime();
AZStd::string oldFingerprint1 = AssetUtilities::GetFileFingerprint(fileEncoded1, "");
AZStd::string oldFingerprint2 = AssetUtilities::GetFileFingerprint(fileEncoded2, "");
AZStd::string oldFingerprint1a = AssetUtilities::GetFileFingerprint(fileEncoded1, "Name1");
AZStd::string oldFingerprint2a = AssetUtilities::GetFileFingerprint(fileEncoded2, "Name2");
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath1, "contents1a"));
EXPECT_TRUE(UnitTestUtils::CreateDummyFile(absoluteTestFilePath2, "contents2a"));
EXPECT_STRNE(oldFingerprint1.c_str(), AssetUtilities::GetFileFingerprint(fileEncoded1, "").c_str());
EXPECT_STRNE(oldFingerprint2.c_str(), AssetUtilities::GetFileFingerprint(fileEncoded2, "").c_str());
EXPECT_STRNE(oldFingerprint1a.c_str(), AssetUtilities::GetFileFingerprint(fileEncoded1, "Name1").c_str());
EXPECT_STRNE(oldFingerprint2a.c_str(), AssetUtilities::GetFileFingerprint(fileEncoded2, "Name2").c_str());
}
TEST_F(AssetUtilitiesTest, GetFileFingerprint_NonExistentFiles)
{
AZStd::string nonExistentFile1 = AZ::Uuid::CreateRandom().ToString<AZStd::string>() + ".txt";
ASSERT_FALSE(QFileInfo::exists(nonExistentFile1.c_str()));
EXPECT_STRNE(AssetUtilities::GetFileFingerprint(nonExistentFile1, "").c_str(), AssetUtilities::GetFileFingerprint(nonExistentFile1, "Name").c_str());
EXPECT_STREQ(AssetUtilities::GetFileFingerprint(nonExistentFile1, "Name").c_str(), AssetUtilities::GetFileFingerprint(nonExistentFile1, "Name").c_str());
}
TEST_F(AssetUtilitiesTest, GetServerAddress_ReadFromConfig_Valid)
{
QTemporaryDir tempDir;
QDir tempPath(tempDir.path());
QString assetServerAddress("T:/AssetServerCacheDummyFolder");
UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("AssetProcessorPlatformConfig.ini"), QString("[Server]\ncacheServerAddress=%1\n").arg(assetServerAddress));
AssetUtilities::ResetAssetRoot();
QDir newRoot;
AssetUtilities::ComputeEngineRoot(newRoot, &tempPath);
QString assetServerAddressReturned = AssetUtilities::ServerAddress();
EXPECT_STREQ(assetServerAddressReturned.toUtf8().data(), assetServerAddress.toUtf8().data());
}
TEST_F(AssetUtilitiesTest, CreateDirWithTimeout_Valid)
{
QTemporaryDir tempDir;
QDir tempPath(tempDir.path());
QDir dir(tempPath.filePath("folder"));
unsigned int timeToWaitInSecs = 3;
AZStd::vector<AZStd::thread*> threadList;
AZStd::vector<bool> resultList;
AZStd::mutex resultMutex;
auto runFunc = [&]()
{
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(100));// sleeping to sync all the threads
bool result = AssetUtilities::CreateDirectoryWithTimeout(dir, timeToWaitInSecs);
AZStd::lock_guard<AZStd::mutex> locker(resultMutex);
resultList.push_back(result);
};
int numberOfThreads = 5;
ASSERT_FALSE(dir.exists());
for (int idx = 0; idx < numberOfThreads; idx++)
{
AZStd::thread* workerThread = new AZStd::thread(runFunc);
threadList.emplace_back(workerThread);
}
for (auto thread : threadList)
{
if (thread->joinable())
{
thread->join();
}
delete thread;
}
for (int idx = 0; idx < numberOfThreads; idx++)
{
ASSERT_TRUE(resultList[idx]);
}
ASSERT_TRUE(dir.exists());
}
TEST_F(AssetUtilitiesTest, CreateDir_InvalidDir_Timeout_Valid)
{
QDir dir(":\folder");
unsigned int timeToWaitInSecs = 1;
AZStd::vector<AZStd::thread*> threadList;
AZStd::vector<bool> resultList;
AZStd::mutex resultMutex;
auto runFunc = [&]()
{
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(100));// sleeping to sync all the threads
bool result = AssetUtilities::CreateDirectoryWithTimeout(dir, timeToWaitInSecs);
AZStd::lock_guard<AZStd::mutex> locker(resultMutex);
resultList.push_back(result);
};
int numberOfThreads = 5;
ASSERT_FALSE(dir.exists());
for (int idx = 0; idx < numberOfThreads; idx++)
{
AZStd::thread* workerThread = new AZStd::thread(runFunc);
threadList.emplace_back(workerThread);
}
for (auto thread : threadList)
{
if (thread->joinable())
{
thread->join();
}
delete thread;
}
for (int idx = 0; idx < numberOfThreads; idx++)
{
ASSERT_FALSE(resultList[idx]);
}
ASSERT_FALSE(dir.exists());
}