//
// Copyright © 2019-2023 Arm Ltd and Contributors. All rights reserved.
// SPDX-License-Identifier: MIT
//

#pragma once

#include <armnn/BackendRegistry.hpp>
#include <armnn/backends/DynamicBackend.hpp>
#include <armnn/backends/ILayerSupport.hpp>
#include <armnn/utility/PolymorphicDowncast.hpp>
#include <backendsCommon/DynamicBackendUtils.hpp>
#include <armnn/backends/TensorHandle.hpp>
#include <armnnUtils/Filesystem.hpp>
#include <reference/workloads/RefConvolution2dWorkload.hpp>
#include <Runtime.hpp>

#include <string>
#include <memory>

#include <doctest/doctest.h>

#if defined(_MSC_VER)
#include <Windows.h>
#endif

#if !defined(DYNAMIC_BACKEND_BUILD_DIR)
#define DYNAMIC_BACKEND_BUILD_DIR fs::path("./")
#endif

static std::string g_TestDirCLI                             = "--dynamic-backend-build-dir";
static std::string g_TestBaseDir                            = "src/backends/backendsCommon/test/";

static std::string g_TestSharedObjectSubDir                 = "testSharedObject/";
static std::string g_TestDynamicBackendSubDir               = "testDynamicBackend/";

static std::string g_TestSharedObjectFileName               = "libTestSharedObject.so";
static std::string g_TestNoSharedObjectFileName             = "libNoSharedObject.txt";

static std::string g_TestValidTestDynamicBackendFileName    = "libValidTestDynamicBackend.so";
static std::string g_TestInvalidTestDynamicBackend1FileName = "libInvalidTestDynamicBackend1.so";
static std::string g_TestInvalidTestDynamicBackend2FileName = "libInvalidTestDynamicBackend2.so";
static std::string g_TestInvalidTestDynamicBackend3FileName = "libInvalidTestDynamicBackend3.so";
static std::string g_TestInvalidTestDynamicBackend4FileName = "libInvalidTestDynamicBackend4.so";
static std::string g_TestInvalidTestDynamicBackend5FileName = "libInvalidTestDynamicBackend5.so";
static std::string g_TestInvalidTestDynamicBackend6FileName = "libInvalidTestDynamicBackend6.so";
static std::string g_TestInvalidTestDynamicBackend7FileName = "libInvalidTestDynamicBackend7.so";

static std::string g_TestValidBackend2FileName              = "Arm_TestValid2_backend.so";
static std::string g_TestValidBackend3FileName              = "Arm_TestValid3_backend.so";
static std::string g_TestValidBackend4FileName              = "Arm_TestValid4_backend.so";
static std::string g_TestValidBackend5FileName              = "Arm_TestValid5_backend.so";
static std::string g_TestInvalidBackend8FileName            = "Arm_TestInvalid8_backend.so";
static std::string g_TestInvalidBackend9FileName            = "Arm_TestInvalid9_backend.so";
static std::string g_TestInvalidBackend10FileName           = "Arm_TestInvalid10_backend.so";
static std::string g_TestInvalidBackend11FileName           = "Arm_TestInvalid11_backend.so";

static std::string g_TestDynamicBackendsSubDir1  = "backendsTestPath1/";
static std::string g_TestDynamicBackendsSubDir2  = "backendsTestPath2/";
static std::string g_TestDynamicBackendsSubDir3  = "backendsTestPath3/";
static std::string g_TestDynamicBackendsSubDir4  = "backendsTestPath4/";
static std::string g_TestDynamicBackendsSubDir5  = "backendsTestPath5/";
static std::string g_TestDynamicBackendsSubDir6  = "backendsTestPath6/";
static std::string g_TestDynamicBackendsSubDir7  = "backendsTestPath7/";
static std::string g_TestDynamicBackendsSubDir8  = "backendsTestPath8/";
static std::string g_TestDynamicBackendsSubDir9  = "backendsTestPath9/";

static std::string g_DynamicBackendsBaseDir                 = "src/backends/dynamic";
static std::string g_ReferenceDynamicBackendSubDir          = "reference/";
static std::string g_ReferenceBackendFileName               = "Arm_CpuRef_backend.so";

// DynamicBackendUtils wrapper class used for testing (allows to directly invoke the protected methods)
class TestDynamicBackendUtils : public armnn::DynamicBackendUtils
{
public:
    static bool IsBackendCompatibleTest(const armnn::BackendVersion& backendApiVersion,
                                        const armnn::BackendVersion& backendVersion)
    {
        return IsBackendCompatibleImpl(backendApiVersion, backendVersion);
    }

    static std::vector<std::string> GetBackendPathsImplTest(const std::string& path)
    {
        return GetBackendPathsImpl(path);
    }

    static armnn::BackendIdSet RegisterDynamicBackendsImplTest(
            armnn::BackendRegistry& backendRegistry,
            const std::vector<armnn::DynamicBackendPtr>& dynamicBackends)
    {
        return RegisterDynamicBackendsImpl(backendRegistry, dynamicBackends);
    }
};

// BackendRegistry wrapper class used for testing (swaps the underlying factory storage)
class TestBackendRegistry : public armnn::BackendRegistry
{
public:
    TestBackendRegistry() : armnn::BackendRegistry()
    {
        Swap(armnn::BackendRegistryInstance(), m_TempStorage);
    }

    ~TestBackendRegistry()
    {
        Swap(armnn::BackendRegistryInstance(), m_TempStorage);
    }

private:
    FactoryStorage m_TempStorage;
};

#if defined(_MSC_VER)
std::string GetUnitTestExecutablePath()
{
    char buffer[MAX_PATH] = "";
    GetModuleFileNameA(NULL, buffer, MAX_PATH);
    fs::path executablePath(buffer);
    return executablePath.parent_path();
}

#else
std::string GetUnitTestExecutablePath()
{
    char buffer[PATH_MAX] = "";
    if (readlink("/proc/self/exe", buffer, PATH_MAX) != -1)
    {
        fs::path executablePath(buffer);
        return executablePath.parent_path();
    }
    return "";
}
#endif

std::string GetBasePath(const std::string& basePath)
{
    using namespace fs;
    // What we're looking for here is the location of the UnitTests executable.
    // Fall back value of current directory.
    path programLocation = GetUnitTestExecutablePath();
    if (!exists(programLocation))
    {
        programLocation = DYNAMIC_BACKEND_BUILD_DIR;
    }

    // This is the base path from the build where the test libraries were built.
    path sharedObjectPath = programLocation.append(basePath);
    REQUIRE_MESSAGE(exists(sharedObjectPath),
                    ("Base path for shared objects does not exist: " + sharedObjectPath.string()));
    return sharedObjectPath.string();
}

std::string GetTestDirectoryBasePath()
{
    return GetBasePath(g_TestBaseDir);
}

std::string GetDynamicBackendsBasePath()
{
    return GetBasePath(g_DynamicBackendsBaseDir);
}

std::string GetTestSubDirectory(const std::string& subdir)
{
    using namespace fs;

    std::string testDynamicBackendsBaseDir = GetTestDirectoryBasePath();
    path testDynamicBackendsBasePath(testDynamicBackendsBaseDir);
    path testDynamicBackendsSubDir = testDynamicBackendsBasePath.append(subdir);
    // Do not check that the sub-directory exists because for testing reasons we may use non-existing paths

    return testDynamicBackendsSubDir.string();
}

std::string GetTestSubDirectory(const std::string& basePath, const std::string& subdir)
{
    using namespace fs;

    path testDynamicBackendsBasePath(basePath);
    path testDynamicBackendsSubDir = testDynamicBackendsBasePath.append(subdir);
    // Do not check that the sub-directory exists because for testing reasons we may use non-existing paths

    return testDynamicBackendsSubDir.string();
}

std::string GetTestFilePath(const std::string& directory, const std::string& fileName)
{
    using namespace fs;

    path directoryPath(directory);
    path fileNamePath = directoryPath.append(fileName);
    CHECK(exists(fileNamePath));

    return fileNamePath.string();
}

void OpenCloseHandleTestImpl()
{
    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestSharedObjectSubDir);
    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestSharedObjectFileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    DynamicBackendUtils::CloseHandle(sharedObjectHandle);
}

void CloseInvalidHandleTestImpl()
{
    using namespace armnn;

    // This calls must silently handle invalid handles and complete successfully (no segfaults, etc.)
    DynamicBackendUtils::CloseHandle(nullptr);
}

void OpenEmptyFileNameTestImpl()
{
    using namespace armnn;

    void* sharedObjectHandle = nullptr;
    CHECK_THROWS_AS(sharedObjectHandle = DynamicBackendUtils::OpenHandle(""), RuntimeException);
    CHECK((sharedObjectHandle == nullptr));
}

void OpenNotExistingFileTestImpl()
{
    using namespace armnn;

    void* sharedObjectHandle = nullptr;
    CHECK_THROWS_AS(sharedObjectHandle = DynamicBackendUtils::OpenHandle("NotExistingFileName"), RuntimeException);
    CHECK((sharedObjectHandle == nullptr));
}

void OpenNotSharedObjectTestImpl()
{
    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestSharedObjectSubDir);
    std::string notSharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestNoSharedObjectFileName);

    void* sharedObjectHandle = nullptr;
    CHECK_THROWS_AS(sharedObjectHandle = DynamicBackendUtils::OpenHandle(notSharedObjectFilePath), RuntimeException);
    CHECK((sharedObjectHandle == nullptr));
}

void GetValidEntryPointTestImpl()
{
    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestSharedObjectSubDir);
    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestSharedObjectFileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    using TestFunctionType = int(*)(int);
    TestFunctionType testFunctionPointer = nullptr;
    CHECK_NOTHROW(testFunctionPointer = DynamicBackendUtils::GetEntryPoint<TestFunctionType>(sharedObjectHandle,
                                                                                                    "TestFunction1"));
    CHECK((testFunctionPointer != nullptr));
    CHECK(testFunctionPointer(7) == 7);

    DynamicBackendUtils::CloseHandle(sharedObjectHandle);
}

void GetNameMangledEntryPointTestImpl()
{
    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestSharedObjectSubDir);
    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestSharedObjectFileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    using TestFunctionType = int(*)(int);
    TestFunctionType testFunctionPointer = nullptr;
    CHECK_THROWS_AS(testFunctionPointer = DynamicBackendUtils::GetEntryPoint<TestFunctionType>(sharedObjectHandle,
                                                                                                 "TestFunction2"),
                      RuntimeException);
    CHECK((testFunctionPointer == nullptr));

    DynamicBackendUtils::CloseHandle(sharedObjectHandle);
}

void GetNoExternEntryPointTestImpl()
{
    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestSharedObjectSubDir);
    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestSharedObjectFileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    using TestFunctionType = int(*)(int);
    TestFunctionType testFunctionPointer = nullptr;
    CHECK_THROWS_AS(testFunctionPointer = DynamicBackendUtils::GetEntryPoint<TestFunctionType>(sharedObjectHandle,
                                                                                                 "TestFunction3"),
                      RuntimeException);
    CHECK((testFunctionPointer == nullptr));

    DynamicBackendUtils::CloseHandle(sharedObjectHandle);
}

void GetNotExistingEntryPointTestImpl()
{
    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestSharedObjectSubDir);
    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestSharedObjectFileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    using TestFunctionType = int(*)(int);
    TestFunctionType testFunctionPointer = nullptr;
    CHECK_THROWS_AS(testFunctionPointer = DynamicBackendUtils::GetEntryPoint<TestFunctionType>(sharedObjectHandle,
                                                                                                 "TestFunction4"),
                      RuntimeException);
    CHECK((testFunctionPointer == nullptr));

    DynamicBackendUtils::CloseHandle(sharedObjectHandle);
}

void BackendVersioningTestImpl()
{
    using namespace armnn;

    // The backend API version used for the tests
    BackendVersion backendApiVersion{ 2, 4 };

    // Same backend and backend API versions are compatible with the backend API
    BackendVersion sameBackendVersion{ 2, 4 };
    CHECK(sameBackendVersion == backendApiVersion);
    CHECK(sameBackendVersion <= backendApiVersion);
    CHECK(TestDynamicBackendUtils::IsBackendCompatibleTest(backendApiVersion, sameBackendVersion) == true);

    // Backend versions that differ from the backend API version by major revision are not compatible
    // with the backend API
    BackendVersion laterMajorBackendVersion{ 3, 4 };
    CHECK(!(laterMajorBackendVersion == backendApiVersion));
    CHECK(!(laterMajorBackendVersion <= backendApiVersion));
    CHECK(TestDynamicBackendUtils::IsBackendCompatibleTest(backendApiVersion, laterMajorBackendVersion) == false);

    BackendVersion earlierMajorBackendVersion{ 1, 4 };
    CHECK(!(earlierMajorBackendVersion == backendApiVersion));
    CHECK(earlierMajorBackendVersion <= backendApiVersion);
    CHECK(TestDynamicBackendUtils::IsBackendCompatibleTest(backendApiVersion,
                                                                earlierMajorBackendVersion) == false);

    // Backend versions with the same major revision but later minor revision than
    // the backend API version are not compatible with the backend API
    BackendVersion laterMinorBackendVersion{ 2, 5 };
    CHECK(!(laterMinorBackendVersion == backendApiVersion));
    CHECK(!(laterMinorBackendVersion <= backendApiVersion));
    CHECK(TestDynamicBackendUtils::IsBackendCompatibleTest(backendApiVersion, laterMinorBackendVersion) == false);

    // Backend versions with the same major revision but earlier minor revision than
    // the backend API version are compatible with the backend API
    BackendVersion earlierMinorBackendVersion{ 2, 3 };
    CHECK(!(earlierMinorBackendVersion == backendApiVersion));
    CHECK(earlierMinorBackendVersion <= backendApiVersion);
    CHECK(TestDynamicBackendUtils::IsBackendCompatibleTest(backendApiVersion, earlierMinorBackendVersion) == true);
}

#if defined(ARMNNREF_ENABLED)
void CreateValidDynamicBackendObjectTestImpl()
{
    // Valid shared object handle
    // Correct name mangling
    // Correct interface
    // Correct backend implementation

    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestDynamicBackendSubDir);

    // We expect this path to exists so we can load a valid dynamic backend.
    CHECK_MESSAGE(fs::exists(testSubDirectory),
                  ("Base path for shared objects does not exist: " + testSubDirectory));

    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestValidTestDynamicBackendFileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    DynamicBackendPtr dynamicBackend;
    CHECK_NOTHROW(dynamicBackend.reset(new DynamicBackend(sharedObjectHandle)));
    CHECK((dynamicBackend != nullptr));

    BackendId dynamicBackendId;
    CHECK_NOTHROW(dynamicBackendId = dynamicBackend->GetBackendId());
    CHECK((dynamicBackendId == "ValidTestDynamicBackend"));

    BackendVersion dynamicBackendVersion;
    CHECK_NOTHROW(dynamicBackendVersion = dynamicBackend->GetBackendVersion());
    CHECK((dynamicBackendVersion == IBackendInternal::GetApiVersion()));

    IBackendInternalUniquePtr dynamicBackendInstance1;
    CHECK_NOTHROW(dynamicBackendInstance1 = dynamicBackend->GetBackend());
    CHECK((dynamicBackendInstance1 != nullptr));

    BackendRegistry::FactoryFunction dynamicBackendFactoryFunction = nullptr;
    CHECK_NOTHROW(dynamicBackendFactoryFunction = dynamicBackend->GetFactoryFunction());
    CHECK((dynamicBackendFactoryFunction != nullptr));

    IBackendInternalUniquePtr dynamicBackendInstance2;
    CHECK_NOTHROW(dynamicBackendInstance2 = dynamicBackendFactoryFunction());
    CHECK((dynamicBackendInstance2 != nullptr));

    CHECK((dynamicBackendInstance1->GetId() == "ValidTestDynamicBackend"));
    CHECK((dynamicBackendInstance2->GetId() == "ValidTestDynamicBackend"));
}
#endif

void CreateDynamicBackendObjectInvalidHandleTestImpl()
{
    // Invalid (null) shared object handle

    using namespace armnn;

    void* sharedObjectHandle = nullptr;
    DynamicBackendPtr dynamicBackend;
    CHECK_THROWS_AS(dynamicBackend.reset(new DynamicBackend(sharedObjectHandle)), InvalidArgumentException);
    CHECK((dynamicBackend == nullptr));
}

void CreateDynamicBackendObjectInvalidInterface1TestImpl()
{
    // Valid shared object handle
    // Wrong (not C-style) name mangling

    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestDynamicBackendSubDir);
    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestInvalidTestDynamicBackend1FileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    DynamicBackendPtr dynamicBackend;
    CHECK_THROWS_AS(dynamicBackend.reset(new DynamicBackend(sharedObjectHandle)), RuntimeException);
    CHECK((dynamicBackend == nullptr));
}

void CreateDynamicBackendObjectInvalidInterface2TestImpl()
{
    // Valid shared object handle
    // Correct name mangling
    // Wrong interface (missing GetBackendId())

    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestDynamicBackendSubDir);
    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestInvalidTestDynamicBackend2FileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    DynamicBackendPtr dynamicBackend;
    CHECK_THROWS_AS(dynamicBackend.reset(new DynamicBackend(sharedObjectHandle)), RuntimeException);
    CHECK((dynamicBackend == nullptr));
}

void CreateDynamicBackendObjectInvalidInterface3TestImpl()
{
    // Valid shared object handle
    // Correct name mangling
    // Wrong interface (missing GetVersion())

    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestDynamicBackendSubDir);
    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestInvalidTestDynamicBackend3FileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    DynamicBackendPtr dynamicBackend;
    CHECK_THROWS_AS(dynamicBackend.reset(new DynamicBackend(sharedObjectHandle)), RuntimeException);
    CHECK((dynamicBackend == nullptr));
}

void CreateDynamicBackendObjectInvalidInterface4TestImpl()
{
    // Valid shared object handle
    // Correct name mangling
    // Wrong interface (missing BackendFactory())

    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestDynamicBackendSubDir);
    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestInvalidTestDynamicBackend4FileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    DynamicBackendPtr dynamicBackend;
    CHECK_THROWS_AS(dynamicBackend.reset(new DynamicBackend(sharedObjectHandle)), RuntimeException);
    CHECK((dynamicBackend == nullptr));
}

void CreateDynamicBackendObjectInvalidInterface5TestImpl()
{
    // Valid shared object handle
    // Correct name mangling
    // Correct interface
    // Invalid (null) backend id returned by GetBackendId()

    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestDynamicBackendSubDir);
    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestInvalidTestDynamicBackend5FileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    DynamicBackendPtr dynamicBackend;
    CHECK_THROWS_AS(dynamicBackend.reset(new DynamicBackend(sharedObjectHandle)), RuntimeException);
    CHECK((dynamicBackend == nullptr));
}

void CreateDynamicBackendObjectInvalidInterface6TestImpl()
{
    // Valid shared object handle
    // Correct name mangling
    // Correct interface
    // Invalid (null) backend instance returned by BackendFactory()

    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestDynamicBackendSubDir);
    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestInvalidTestDynamicBackend6FileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    DynamicBackendPtr dynamicBackend;
    CHECK_NOTHROW(dynamicBackend.reset(new DynamicBackend(sharedObjectHandle)));
    CHECK((dynamicBackend != nullptr));

    BackendId dynamicBackendId;
    CHECK_NOTHROW(dynamicBackendId = dynamicBackend->GetBackendId());
    CHECK((dynamicBackendId == "InvalidTestDynamicBackend"));

    BackendVersion dynamicBackendVersion;
    CHECK_NOTHROW(dynamicBackendVersion = dynamicBackend->GetBackendVersion());
    CHECK((dynamicBackendVersion >= BackendVersion({ 1, 0 })));

    IBackendInternalUniquePtr dynamicBackendInstance1;
    CHECK_THROWS_AS(dynamicBackendInstance1 = dynamicBackend->GetBackend(), RuntimeException);
    CHECK((dynamicBackendInstance1 == nullptr));

    BackendRegistry::FactoryFunction dynamicBackendFactoryFunction = nullptr;
    CHECK_NOTHROW(dynamicBackendFactoryFunction = dynamicBackend->GetFactoryFunction());
    CHECK((dynamicBackendFactoryFunction != nullptr));

    IBackendInternalUniquePtr dynamicBackendInstance2;
    CHECK_THROWS_AS(dynamicBackendInstance2 = dynamicBackendFactoryFunction(), RuntimeException);
    CHECK((dynamicBackendInstance2 == nullptr));
}

void CreateDynamicBackendObjectInvalidInterface7TestImpl()
{
    // Valid shared object handle
    // Correct name mangling
    // Correct interface
    // Invalid (incompatible backend API version) backend instance returned by BackendFactory()

    using namespace armnn;

    std::string testSubDirectory = GetTestSubDirectory(g_TestDynamicBackendSubDir);
    std::string sharedObjectFilePath = GetTestFilePath(testSubDirectory, g_TestInvalidTestDynamicBackend7FileName);

    void* sharedObjectHandle = nullptr;
    CHECK_NOTHROW(sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObjectFilePath));
    CHECK((sharedObjectHandle != nullptr));

    DynamicBackendPtr dynamicBackend;
    CHECK_THROWS_AS(dynamicBackend.reset(new DynamicBackend(sharedObjectHandle)), RuntimeException);
    CHECK((dynamicBackend == nullptr));
}

void GetBackendPathsTestImpl()
{
    using namespace armnn;
    using namespace fs;

    // The test covers four directories:
    // <unit test path>/src/backends/backendsCommon/test/
    //                                                ├─ backendsTestPath1/   -> exists, contains files
    //                                                ├─ backendsTestPath2/   -> exists, contains files
    //                                                ├─ backendsTestPath3/   -> exists, but empty
    //                                                └─ backendsTestPath4/   -> does not exist

    std::string subDir1 = GetTestSubDirectory(g_TestDynamicBackendsSubDir1);
    std::string subDir2 = GetTestSubDirectory(g_TestDynamicBackendsSubDir2);
    std::string subDir3 = GetTestSubDirectory(g_TestDynamicBackendsSubDir3);
    std::string subDir4 = GetTestSubDirectory(g_TestDynamicBackendsSubDir4);

    CHECK(exists(subDir1));
    CHECK(exists(subDir2));
    CHECK(exists(subDir3));
    CHECK(!exists(subDir4));

    // No path
    CHECK(TestDynamicBackendUtils::GetBackendPathsImplTest("").empty());

    // Malformed path
    std::string malformedDir(subDir1 + "/" + subDir1);
    CHECK(TestDynamicBackendUtils::GetBackendPathsImplTest(malformedDir).size()==0);

    // Single valid path
    std::vector<std::string> DynamicBackendPaths2 = TestDynamicBackendUtils::GetBackendPathsImplTest(subDir1);
    CHECK(DynamicBackendPaths2.size() == 1);
    CHECK(DynamicBackendPaths2[0] == subDir1);

    // Multiple equal and valid paths
    std::string multipleEqualDirs(subDir1 + ":" + subDir1);
    std::vector<std::string> DynamicBackendPaths3 = TestDynamicBackendUtils::GetBackendPathsImplTest(multipleEqualDirs);
    CHECK(DynamicBackendPaths3.size() == 1);
    CHECK(DynamicBackendPaths3[0] == subDir1);

    // Multiple empty paths
    CHECK(TestDynamicBackendUtils::GetBackendPathsImplTest(":::").empty());

    // Multiple valid paths
    std::string multipleValidPaths(subDir1 + ":" + subDir2 + ":" + subDir3);
    std::vector<std::string> DynamicBackendPaths5 =
        TestDynamicBackendUtils::GetBackendPathsImplTest(multipleValidPaths);
    CHECK(DynamicBackendPaths5.size() == 3);
    CHECK(DynamicBackendPaths5[0] == subDir1);
    CHECK(DynamicBackendPaths5[1] == subDir2);
    CHECK(DynamicBackendPaths5[2] == subDir3);

    // Valid among empty paths
    std::string validAmongEmptyDirs("::" + subDir1 + ":");
    std::vector<std::string> DynamicBackendPaths6 =
        TestDynamicBackendUtils::GetBackendPathsImplTest(validAmongEmptyDirs);
    CHECK(DynamicBackendPaths6.size() == 1);
    CHECK(DynamicBackendPaths6[0] == subDir1);

    // Invalid among empty paths
    std::string invalidAmongEmptyDirs(":" + subDir4 + "::");
    CHECK(TestDynamicBackendUtils::GetBackendPathsImplTest(invalidAmongEmptyDirs).empty());

    // Valid, invalid and empty paths
    std::string validInvalidEmptyDirs(subDir1 + ":" + subDir4 + ":");
    std::vector<std::string> DynamicBackendPaths8 =
        TestDynamicBackendUtils::GetBackendPathsImplTest(validInvalidEmptyDirs);
    CHECK(DynamicBackendPaths8.size() == 1);
    CHECK(DynamicBackendPaths8[0] == subDir1);

    // Mix of duplicates of valid, invalid and empty paths
    std::string duplicateValidInvalidEmptyDirs(validInvalidEmptyDirs + ":" + validInvalidEmptyDirs + ":" +
                                               subDir2 + ":" + subDir2);
    std::vector<std::string> DynamicBackendPaths9 =
        TestDynamicBackendUtils::GetBackendPathsImplTest(duplicateValidInvalidEmptyDirs);
    CHECK(DynamicBackendPaths9.size() == 2);
    CHECK(DynamicBackendPaths9[0] == subDir1);
    CHECK(DynamicBackendPaths9[1] == subDir2);
}

void GetBackendPathsOverrideTestImpl()
{
    using namespace armnn;
    using namespace fs;

    std::string subDir1 = GetTestSubDirectory(g_TestDynamicBackendsSubDir1);
    std::string subDir4 = GetTestSubDirectory(g_TestDynamicBackendsSubDir4);

    CHECK(exists(subDir1));
    CHECK(!exists(subDir4));

    // Override with valid path
    std::vector<std::string> validResult = DynamicBackendUtils::GetBackendPaths(subDir1);
    CHECK(validResult.size() == 1);
    CHECK(validResult[0] == subDir1);

    // Override with invalid path
    std::vector<std::string> invalidResult = DynamicBackendUtils::GetBackendPaths(subDir4);
    CHECK(invalidResult.empty());
}

void GetSharedObjectsTestImpl()
{
    using namespace armnn;
    using namespace fs;

    // The test covers four directories:
    // <unit test path>/src/backends/backendsCommon/test/
    //                                                ├─ backendsTestPath1/   -> exists, contains files
    //                                                ├─ backendsTestPath2/   -> exists, contains files
    //                                                ├─ backendsTestPath3/   -> exists, but empty
    //                                                └─ backendsTestPath4/   -> does not exist
    //
    // The test sub-directory backendsTestPath1/ contains the following test files:
    //
    // Arm_GpuAcc_backend.so                                       -> valid (basic backend name)
    // Arm_GpuAcc_backend.so.1                                     -> valid (single field version number)
    // Arm_GpuAcc_backend.so.1.2                                   -> valid (multiple field version number)
    // Arm_GpuAcc_backend.so.1.2.3                                 -> valid (multiple field version number)
    // Arm_GpuAcc_backend.so.10.1.27                               -> valid (Multiple digit version)
    // Arm_GpuAcc_backend.so.10.1.33.                              -> not valid (dot not followed by version number)
    // Arm_GpuAcc_backend.so.3.4..5                                -> not valid (dot not followed by version number)
    // Arm_GpuAcc_backend.so.1,1.1                                 -> not valid (comma instead of dot in the version)
    //
    // Arm123_GpuAcc_backend.so                                    -> valid (digits in vendor name are allowed)
    // Arm_GpuAcc456_backend.so                                    -> valid (digits in backend id are allowed)
    // Arm%Co_GpuAcc_backend.so                                    -> not valid (invalid character in vendor name)
    // Arm_Gpu.Acc_backend.so                                      -> not valid (invalid character in backend id)
    //
    // GpuAcc_backend.so                                           -> not valid (missing vendor name)
    // _GpuAcc_backend.so                                          -> not valid (missing vendor name)
    // Arm__backend.so                                             -> not valid (missing backend id)
    // Arm_GpuAcc.so                                               -> not valid (missing "backend" at the end)
    // __backend.so                                                -> not valid (missing vendor name and backend id)
    // __.so                                                       -> not valid (missing all fields)
    //
    // Arm_GpuAcc_backend                                          -> not valid (missing at least ".so" at the end)
    // Arm_GpuAcc_backend_v1.2.so                                  -> not valid (extra version info at the end)
    //
    // The test sub-directory backendsTestPath1/ contains the following test files:
    //
    // Arm_CpuAcc_backend.so                                       -> valid (basic backend name)
    // Arm_CpuAcc_backend.so.1 -> Arm_CpuAcc_backend.so            -> valid (symlink to valid backend file)
    // Arm_CpuAcc_backend.so.1.2 -> Arm_CpuAcc_backend.so.1        -> valid (symlink to valid symlink)
    // Arm_CpuAcc_backend.so.1.2.3 -> Arm_CpuAcc_backend.so.1.2    -> valid (symlink to valid symlink)
    //
    // Arm_no_backend.so -> nothing                                -> not valid (symlink resolves to non-existent file)
    //
    // Arm_GpuAcc_backend.so                                       -> valid (but duplicated from backendsTestPath1/)

    std::string testDynamicBackendsSubDir1 = GetTestSubDirectory(g_TestDynamicBackendsSubDir1);
    std::string testDynamicBackendsSubDir2 = GetTestSubDirectory(g_TestDynamicBackendsSubDir2);
    std::string testDynamicBackendsSubDir3 = GetTestSubDirectory(g_TestDynamicBackendsSubDir3);
    std::string testDynamicBackendsSubDir4 = GetTestSubDirectory(g_TestDynamicBackendsSubDir4);
    CHECK(exists(testDynamicBackendsSubDir1));
    CHECK(exists(testDynamicBackendsSubDir2));
    CHECK(exists(testDynamicBackendsSubDir3));
    CHECK(!exists(testDynamicBackendsSubDir4));

    std::vector<std::string> backendPaths
    {
        testDynamicBackendsSubDir1,
        testDynamicBackendsSubDir2,
        testDynamicBackendsSubDir3,
        testDynamicBackendsSubDir4
    };
    std::vector<std::string> sharedObjects = DynamicBackendUtils::GetSharedObjects(backendPaths);
    std::vector<fs::path> expectedSharedObjects
    {
        path(testDynamicBackendsSubDir1 + "Arm123_GpuAcc_backend.so"),      // Digits in vendor name are allowed
        path(testDynamicBackendsSubDir1 + "Arm_GpuAcc456_backend.so"),      // Digits in backend id are allowed
        path(testDynamicBackendsSubDir1 + "Arm_GpuAcc_backend.so"),         // Basic backend name
        path(testDynamicBackendsSubDir1 + "Arm_GpuAcc_backend.so.1"),       // Single field version number
        path(testDynamicBackendsSubDir1 + "Arm_GpuAcc_backend.so.1.2"),     // Multiple field version number
        path(testDynamicBackendsSubDir1 + "Arm_GpuAcc_backend.so.1.2.3"),   // Multiple field version number
        path(testDynamicBackendsSubDir1 + "Arm_GpuAcc_backend.so.10.1.27"), // Multiple digit version
        path(testDynamicBackendsSubDir2 + "Arm_CpuAcc_backend.so"),         // Duplicate symlinks removed
        path(testDynamicBackendsSubDir2 + "Arm_GpuAcc_backend.so")          // Duplicates on different paths are allowed
    };

    CHECK(sharedObjects.size() == expectedSharedObjects.size());
    CHECK(fs::equivalent(path(sharedObjects[0]), expectedSharedObjects[0]));
    CHECK(fs::equivalent(path(sharedObjects[1]), expectedSharedObjects[1]));
    CHECK(fs::equivalent(path(sharedObjects[2]), expectedSharedObjects[2]));
    CHECK(fs::equivalent(path(sharedObjects[3]), expectedSharedObjects[3]));
    CHECK(fs::equivalent(path(sharedObjects[4]), expectedSharedObjects[4]));
    CHECK(fs::equivalent(path(sharedObjects[5]), expectedSharedObjects[5]));
    CHECK(fs::equivalent(path(sharedObjects[6]), expectedSharedObjects[6]));
    CHECK(fs::equivalent(path(sharedObjects[7]), expectedSharedObjects[7]));
    CHECK(fs::equivalent(path(sharedObjects[8]), expectedSharedObjects[8]));
}

void CreateDynamicBackendsTestImpl()
{
    using namespace armnn;
    using namespace fs;

    // The test covers four directories:
    // <unit test path>/src/backends/backendsCommon/test/
    //                                                ├─ backendsTestPath5/   -> exists, contains files
    //                                                ├─ backendsTestPath6/   -> exists, contains files
    //                                                ├─ backendsTestPath7/   -> exists, but empty
    //                                                └─ backendsTestPath8/   -> does not exist
    //
    // The test sub-directory backendsTestPath5/ contains the following test files:
    //
    // Arm_TestValid2_backend.so   -> valid (basic backend name)
    // Arm_TestValid3_backend.so   -> valid (basic backend name)
    // Arm_TestInvalid8_backend.so -> not valid (invalid backend id)
    //
    // The test sub-directory backendsTestPath6/ contains the following test files:
    //
    // Arm_TestValid2_backend.so   -> valid (but duplicated from backendsTestPath5/)
    // Arm_TestValid4_backend.so   -> valid (it has a different filename,
    //                                       but it has the same backend id of Arm_TestValid2_backend.so
    //                                       and the same version)
    // Arm_TestValid5_backend.so   -> valid (basic backend name)
    // Arm_TestInvalid9_backend.so -> not valid (it has a different filename,
    //                                           but it has the same backend id of Arm_TestValid2_backend.so
    //                                           and a version incompatible with the Backend API)

    std::string testDynamicBackendsSubDir5 = GetTestSubDirectory(g_TestDynamicBackendsSubDir5);
    std::string testDynamicBackendsSubDir6 = GetTestSubDirectory(g_TestDynamicBackendsSubDir6);
    std::string testDynamicBackendsSubDir7 = GetTestSubDirectory(g_TestDynamicBackendsSubDir7);
    std::string testDynamicBackendsSubDir8 = GetTestSubDirectory(g_TestDynamicBackendsSubDir8);
    CHECK(exists(testDynamicBackendsSubDir5));
    CHECK(exists(testDynamicBackendsSubDir6));
    CHECK(exists(testDynamicBackendsSubDir7));
    CHECK(!exists(testDynamicBackendsSubDir8));

    std::vector<std::string> backendPaths
    {
        testDynamicBackendsSubDir5,
        testDynamicBackendsSubDir6,
        testDynamicBackendsSubDir7,
        testDynamicBackendsSubDir8
    };
    std::vector<std::string> sharedObjects = DynamicBackendUtils::GetSharedObjects(backendPaths);
    std::vector<DynamicBackendPtr> dynamicBackends = DynamicBackendUtils::CreateDynamicBackends(sharedObjects);

    CHECK(dynamicBackends.size() == 5);
    CHECK((dynamicBackends[0] != nullptr));
    CHECK((dynamicBackends[1] != nullptr));
    CHECK((dynamicBackends[2] != nullptr));
    CHECK((dynamicBackends[3] != nullptr));
    CHECK((dynamicBackends[4] != nullptr));

    // Duplicates are allowed here, they will be skipped later during the backend registration
    CHECK((dynamicBackends[0]->GetBackendId() == "TestValid2"));
    CHECK((dynamicBackends[1]->GetBackendId() == "TestValid3"));
    CHECK((dynamicBackends[2]->GetBackendId() == "TestValid2")); // From duplicate Arm_TestValid2_backend.so
    CHECK((dynamicBackends[3]->GetBackendId() == "TestValid2")); // From Arm_TestValid4_backend.so
    CHECK((dynamicBackends[4]->GetBackendId() == "TestValid5"));
}

void CreateDynamicBackendsNoPathsTestImpl()
{
    using namespace armnn;

    std::vector<DynamicBackendPtr> dynamicBackends = DynamicBackendUtils::CreateDynamicBackends({});

    CHECK(dynamicBackends.empty());
}

void CreateDynamicBackendsAllInvalidTestImpl()
{
    using namespace armnn;

    std::vector<std::string> sharedObjects
    {
        "InvalidSharedObject1",
        "InvalidSharedObject2",
        "InvalidSharedObject3",
    };
    std::vector<DynamicBackendPtr> dynamicBackends = DynamicBackendUtils::CreateDynamicBackends(sharedObjects);

    CHECK(dynamicBackends.empty());
}

void CreateDynamicBackendsMixedTypesTestImpl()
{
    using namespace armnn;
    using namespace fs;

    std::string testDynamicBackendsSubDir5 = GetTestSubDirectory(g_TestDynamicBackendsSubDir5);
    std::string testDynamicBackendsSubDir6 = GetTestSubDirectory(g_TestDynamicBackendsSubDir6);
    CHECK(exists(testDynamicBackendsSubDir5));
    CHECK(exists(testDynamicBackendsSubDir6));

    std::string testValidBackend2FilePath = GetTestFilePath(testDynamicBackendsSubDir5,
                                                            g_TestValidBackend2FileName);
    std::string testInvalidBackend8FilePath = GetTestFilePath(testDynamicBackendsSubDir5,
                                                              g_TestInvalidBackend8FileName);
    std::string testInvalidBackend9FilePath = GetTestFilePath(testDynamicBackendsSubDir6,
                                                              g_TestInvalidBackend9FileName);
    CHECK(exists(testValidBackend2FilePath));
    CHECK(exists(testInvalidBackend8FilePath));
    CHECK(exists(testInvalidBackend9FilePath));

    std::vector<std::string> sharedObjects
    {
        testValidBackend2FilePath,   // Arm_TestValid2_backend.so     -> valid (basic backend name)
        testInvalidBackend8FilePath, // Arm_TestInvalid8_backend.so   -> not valid (invalid backend id)
        testInvalidBackend9FilePath, // Arm_TestInvalid9_backend.so   -> not valid (incompatible version)
        "InvalidSharedObject",       // The file does not exist
    };
    std::vector<DynamicBackendPtr> dynamicBackends = DynamicBackendUtils::CreateDynamicBackends(sharedObjects);

    CHECK(dynamicBackends.size() == 1);
    CHECK((dynamicBackends[0] != nullptr));
    CHECK((dynamicBackends[0]->GetBackendId() == "TestValid2"));
}

#if defined(ARMNNREF_ENABLED)
void RegisterSingleDynamicBackendTestImpl()
{
    using namespace armnn;
    using namespace fs;

    // Register one valid dynamic backend

    // Dummy registry used for testing
    BackendRegistry backendRegistry;
    CHECK(backendRegistry.Size() == 0);

    std::string testDynamicBackendsSubDir5 = GetTestSubDirectory(g_TestDynamicBackendsSubDir5);
    CHECK(exists(testDynamicBackendsSubDir5));

    std::string testValidBackend2FilePath = GetTestFilePath(testDynamicBackendsSubDir5, g_TestValidBackend2FileName);
    CHECK(exists(testValidBackend2FilePath));

    std::vector<std::string> sharedObjects{ testValidBackend2FilePath };
    std::vector<DynamicBackendPtr> dynamicBackends = TestDynamicBackendUtils::CreateDynamicBackends(sharedObjects);

    CHECK(dynamicBackends.size() == 1);
    CHECK((dynamicBackends[0] != nullptr));

    BackendId dynamicBackendId = dynamicBackends[0]->GetBackendId();
    CHECK((dynamicBackendId == "TestValid2"));

    BackendVersion dynamicBackendVersion = dynamicBackends[0]->GetBackendVersion();
    CHECK(TestDynamicBackendUtils::IsBackendCompatible(dynamicBackendVersion));

    BackendIdSet registeredBackendIds = TestDynamicBackendUtils::RegisterDynamicBackendsImplTest(backendRegistry,
                                                                                                 dynamicBackends);
    CHECK(backendRegistry.Size() == 1);
    CHECK(registeredBackendIds.size() == 1);

    BackendIdSet backendIds = backendRegistry.GetBackendIds();
    CHECK(backendIds.size() == 1);
    CHECK((backendIds.find(dynamicBackendId) != backendIds.end()));
    CHECK((registeredBackendIds.find(dynamicBackendId) != registeredBackendIds.end()));

    auto dynamicBackendFactoryFunction = backendRegistry.GetFactory(dynamicBackendId);
    CHECK((dynamicBackendFactoryFunction != nullptr));

    IBackendInternalUniquePtr dynamicBackend = dynamicBackendFactoryFunction();
    CHECK((dynamicBackend != nullptr));
    CHECK((dynamicBackend->GetId() == dynamicBackendId));
}

void RegisterMultipleDynamicBackendsTestImpl()
{
    using namespace armnn;
    using namespace fs;

    // Register many valid dynamic backends

    std::string testDynamicBackendsSubDir5 = GetTestSubDirectory(g_TestDynamicBackendsSubDir5);
    std::string testDynamicBackendsSubDir6 = GetTestSubDirectory(g_TestDynamicBackendsSubDir6);
    CHECK(exists(testDynamicBackendsSubDir5));
    CHECK(exists(testDynamicBackendsSubDir6));

    std::string testValidBackend2FilePath = GetTestFilePath(testDynamicBackendsSubDir5, g_TestValidBackend2FileName);
    std::string testValidBackend3FilePath = GetTestFilePath(testDynamicBackendsSubDir5, g_TestValidBackend3FileName);
    std::string testValidBackend5FilePath = GetTestFilePath(testDynamicBackendsSubDir6, g_TestValidBackend5FileName);
    CHECK(exists(testValidBackend2FilePath));
    CHECK(exists(testValidBackend3FilePath));
    CHECK(exists(testValidBackend5FilePath));

    std::vector<std::string> sharedObjects
    {
        testValidBackend2FilePath,
        testValidBackend3FilePath,
        testValidBackend5FilePath
    };
    std::vector<DynamicBackendPtr> dynamicBackends = TestDynamicBackendUtils::CreateDynamicBackends(sharedObjects);

    CHECK(dynamicBackends.size() == 3);
    CHECK((dynamicBackends[0] != nullptr));
    CHECK((dynamicBackends[1] != nullptr));
    CHECK((dynamicBackends[2] != nullptr));

    BackendId dynamicBackendId1 = dynamicBackends[0]->GetBackendId();
    BackendId dynamicBackendId2 = dynamicBackends[1]->GetBackendId();
    BackendId dynamicBackendId3 = dynamicBackends[2]->GetBackendId();
    CHECK((dynamicBackendId1 == "TestValid2"));
    CHECK((dynamicBackendId2 == "TestValid3"));
    CHECK((dynamicBackendId3 == "TestValid5"));

    for (size_t i = 0; i < dynamicBackends.size(); i++)
    {
        BackendVersion dynamicBackendVersion = dynamicBackends[i]->GetBackendVersion();
        CHECK(TestDynamicBackendUtils::IsBackendCompatible(dynamicBackendVersion));
    }

    // Dummy registry used for testing
    BackendRegistry backendRegistry;
    CHECK(backendRegistry.Size() == 0);

    BackendIdSet registeredBackendIds = TestDynamicBackendUtils::RegisterDynamicBackendsImplTest(backendRegistry,
                                                                                                 dynamicBackends);
    CHECK(backendRegistry.Size() == 3);
    CHECK(registeredBackendIds.size() == 3);

    BackendIdSet backendIds = backendRegistry.GetBackendIds();
    CHECK(backendIds.size() == 3);
    CHECK((backendIds.find(dynamicBackendId1) != backendIds.end()));
    CHECK((backendIds.find(dynamicBackendId2) != backendIds.end()));
    CHECK((backendIds.find(dynamicBackendId3) != backendIds.end()));
    CHECK((registeredBackendIds.find(dynamicBackendId1) != registeredBackendIds.end()));
    CHECK((registeredBackendIds.find(dynamicBackendId2) != registeredBackendIds.end()));
    CHECK((registeredBackendIds.find(dynamicBackendId3) != registeredBackendIds.end()));

    for (size_t i = 0; i < dynamicBackends.size(); i++)
    {
        BackendId dynamicBackendId = dynamicBackends[i]->GetBackendId();

        auto dynamicBackendFactoryFunction = backendRegistry.GetFactory(dynamicBackendId);
        CHECK((dynamicBackendFactoryFunction != nullptr));

        IBackendInternalUniquePtr dynamicBackend = dynamicBackendFactoryFunction();
        CHECK((dynamicBackend != nullptr));
        CHECK((dynamicBackend->GetId() == dynamicBackendId));
    }
}

void RegisterMixedDynamicBackendsTestImpl()
{
    using namespace armnn;
    using namespace fs;

    // The test covers five directories:
    // <unit test path>/src/backends/backendsCommon/test/
    //                                                ├─ backendsTestPath5/   -> exists, contains files
    //                                                ├─ backendsTestPath6/   -> exists, contains files
    //                                                ├─ backendsTestPath7/   -> exists, but empty
    //                                                ├─ backendsTestPath8/   -> does not exist
    //                                                └─ backendsTestPath9/   -> exists, contains files
    //
    // The test sub-directory backendsTestPath5/ contains the following test files:
    //
    // Arm_TestValid2_backend.so   -> valid (basic backend name)
    // Arm_TestValid3_backend.so   -> valid (basic backend name)
    // Arm_TestInvalid8_backend.so -> not valid (invalid backend id)
    //
    // The test sub-directory backendsTestPath6/ contains the following test files:
    //
    // Arm_TestValid2_backend.so   -> valid (but duplicated from backendsTestPath5/)
    // Arm_TestValid4_backend.so   -> valid (it has a different filename,
    //                                       but it has the same backend id of Arm_TestValid2_backend.so
    //                                       and the same version)
    // Arm_TestValid5_backend.so   -> valid (basic backend name)
    // Arm_TestInvalid9_backend.so -> not valid (it has a different filename,
    //                                           but it has the same backend id of Arm_TestValid2_backend.so
    //                                           and a version incompatible with the Backend API)
    //
    // The test sub-directory backendsTestPath9/ contains the following test files:
    //
    // Arm_TestInvalid10_backend.so -> not valid (empty backend id)
    // Arm_TestInvalid11_backend.so -> not valid ("Unknown" backend id)

    std::string testDynamicBackendsSubDir5 = GetTestSubDirectory(g_TestDynamicBackendsSubDir5);
    std::string testDynamicBackendsSubDir6 = GetTestSubDirectory(g_TestDynamicBackendsSubDir6);
    std::string testDynamicBackendsSubDir7 = GetTestSubDirectory(g_TestDynamicBackendsSubDir7);
    std::string testDynamicBackendsSubDir8 = GetTestSubDirectory(g_TestDynamicBackendsSubDir8);
    std::string testDynamicBackendsSubDir9 = GetTestSubDirectory(g_TestDynamicBackendsSubDir9);
    CHECK(exists(testDynamicBackendsSubDir5));
    CHECK(exists(testDynamicBackendsSubDir6));
    CHECK(exists(testDynamicBackendsSubDir7));
    CHECK(!exists(testDynamicBackendsSubDir8));
    CHECK(exists(testDynamicBackendsSubDir9));

    std::string testValidBackend2FilePath    = GetTestFilePath(testDynamicBackendsSubDir5, g_TestValidBackend2FileName);
    std::string testValidBackend3FilePath    = GetTestFilePath(testDynamicBackendsSubDir5, g_TestValidBackend3FileName);
    std::string testValidBackend2DupFilePath = GetTestFilePath(testDynamicBackendsSubDir6, g_TestValidBackend2FileName);
    std::string testValidBackend4FilePath    = GetTestFilePath(testDynamicBackendsSubDir6, g_TestValidBackend4FileName);
    std::string testValidBackend5FilePath    = GetTestFilePath(testDynamicBackendsSubDir6, g_TestValidBackend5FileName);
    std::string testInvalidBackend8FilePath  = GetTestFilePath(testDynamicBackendsSubDir5,
                                                               g_TestInvalidBackend8FileName);
    std::string testInvalidBackend9FilePath  = GetTestFilePath(testDynamicBackendsSubDir6,
                                                               g_TestInvalidBackend9FileName);
    std::string testInvalidBackend10FilePath = GetTestFilePath(testDynamicBackendsSubDir9,
                                                               g_TestInvalidBackend10FileName);
    std::string testInvalidBackend11FilePath = GetTestFilePath(testDynamicBackendsSubDir9,
                                                               g_TestInvalidBackend11FileName);
    CHECK(exists(testValidBackend2FilePath));
    CHECK(exists(testValidBackend3FilePath));
    CHECK(exists(testValidBackend2DupFilePath));
    CHECK(exists(testValidBackend4FilePath));
    CHECK(exists(testValidBackend5FilePath));
    CHECK(exists(testInvalidBackend8FilePath));
    CHECK(exists(testInvalidBackend9FilePath));
    CHECK(exists(testInvalidBackend10FilePath));
    CHECK(exists(testInvalidBackend11FilePath));

    std::vector<std::string> sharedObjects
    {
        testValidBackend2FilePath,
        testValidBackend3FilePath,
        testValidBackend2DupFilePath,
        testValidBackend4FilePath,
        testValidBackend5FilePath,
        testInvalidBackend8FilePath,
        testInvalidBackend9FilePath,
        testInvalidBackend10FilePath,
        testInvalidBackend11FilePath,
        "InvalidSharedObject"
    };
    std::vector<DynamicBackendPtr> dynamicBackends = TestDynamicBackendUtils::CreateDynamicBackends(sharedObjects);

    CHECK(dynamicBackends.size() == 7);
    CHECK((dynamicBackends[0] != nullptr));
    CHECK((dynamicBackends[1] != nullptr));
    CHECK((dynamicBackends[2] != nullptr));
    CHECK((dynamicBackends[3] != nullptr));
    CHECK((dynamicBackends[4] != nullptr));
    CHECK((dynamicBackends[5] != nullptr));
    CHECK((dynamicBackends[6] != nullptr));

    BackendId dynamicBackendId1 = dynamicBackends[0]->GetBackendId();
    BackendId dynamicBackendId2 = dynamicBackends[1]->GetBackendId();
    BackendId dynamicBackendId3 = dynamicBackends[2]->GetBackendId();
    BackendId dynamicBackendId4 = dynamicBackends[3]->GetBackendId();
    BackendId dynamicBackendId5 = dynamicBackends[4]->GetBackendId();
    BackendId dynamicBackendId6 = dynamicBackends[5]->GetBackendId();
    BackendId dynamicBackendId7 = dynamicBackends[6]->GetBackendId();
    CHECK((dynamicBackendId1 == "TestValid2"));
    CHECK((dynamicBackendId2 == "TestValid3"));
    CHECK((dynamicBackendId3 == "TestValid2")); // From duplicate Arm_TestValid2_backend.so
    CHECK((dynamicBackendId4 == "TestValid2")); // From Arm_TestValid4_backend.so
    CHECK((dynamicBackendId5 == "TestValid5"));
    CHECK((dynamicBackendId6 == ""));
    CHECK((dynamicBackendId7 == "Unknown"));

    for (size_t i = 0; i < dynamicBackends.size(); i++)
    {
        BackendVersion dynamicBackendVersion = dynamicBackends[i]->GetBackendVersion();
        CHECK(TestDynamicBackendUtils::IsBackendCompatible(dynamicBackendVersion));
    }

    // Dummy registry used for testing
    BackendRegistry backendRegistry;
    CHECK(backendRegistry.Size() == 0);

    std::vector<BackendId> expectedRegisteredbackendIds
    {
        "TestValid2",
        "TestValid3",
        "TestValid5"
    };

    BackendIdSet registeredBackendIds = TestDynamicBackendUtils::RegisterDynamicBackendsImplTest(backendRegistry,
                                                                                                 dynamicBackends);
    CHECK(backendRegistry.Size() == expectedRegisteredbackendIds.size());
    CHECK(registeredBackendIds.size() == expectedRegisteredbackendIds.size());

    BackendIdSet backendIds = backendRegistry.GetBackendIds();
    CHECK(backendIds.size() == expectedRegisteredbackendIds.size());
    for (const BackendId& expectedRegisteredbackendId : expectedRegisteredbackendIds)
    {
        CHECK((backendIds.find(expectedRegisteredbackendId) != backendIds.end()));
        CHECK((registeredBackendIds.find(expectedRegisteredbackendId) != registeredBackendIds.end()));

        auto dynamicBackendFactoryFunction = backendRegistry.GetFactory(expectedRegisteredbackendId);
        CHECK((dynamicBackendFactoryFunction != nullptr));

        IBackendInternalUniquePtr dynamicBackend = dynamicBackendFactoryFunction();
        CHECK((dynamicBackend != nullptr));
        CHECK((dynamicBackend->GetId() == expectedRegisteredbackendId));
    }
}
#endif

void RegisterMultipleInvalidDynamicBackendsTestImpl()
{
    using namespace armnn;
    using namespace fs;

    // Try to register many invalid dynamic backends

    // The test covers one directory:
    // <unit test path>/src/backends/backendsCommon/test/
    //                                                └─ backendsTestPath9/   -> exists, contains files
    //
    // The test sub-directory backendsTestPath9/ contains the following test files:
    //
    // Arm_TestInvalid10_backend.so -> not valid (invalid backend id)
    // Arm_TestInvalid11_backend.so -> not valid (invalid backend id)

    std::string testDynamicBackendsSubDir9 = GetTestSubDirectory(g_TestDynamicBackendsSubDir9);
    CHECK(exists(testDynamicBackendsSubDir9));

    std::string testInvalidBackend10FilePath = GetTestFilePath(testDynamicBackendsSubDir9,
                                                               g_TestInvalidBackend10FileName);
    std::string testInvalidBackend11FilePath = GetTestFilePath(testDynamicBackendsSubDir9,
                                                               g_TestInvalidBackend11FileName);
    CHECK(exists(testInvalidBackend10FilePath));
    CHECK(exists(testInvalidBackend11FilePath));

    std::vector<std::string> sharedObjects
    {
        testInvalidBackend10FilePath,
        testInvalidBackend11FilePath,
        "InvalidSharedObject"
    };
    std::vector<DynamicBackendPtr> dynamicBackends = TestDynamicBackendUtils::CreateDynamicBackends(sharedObjects);

    CHECK(dynamicBackends.size() == 2);
    CHECK((dynamicBackends[0] != nullptr));
    CHECK((dynamicBackends[1] != nullptr));

    BackendId dynamicBackendId1 = dynamicBackends[0]->GetBackendId();
    BackendId dynamicBackendId2 = dynamicBackends[1]->GetBackendId();
    CHECK((dynamicBackendId1 == ""));
    CHECK((dynamicBackendId2 == "Unknown"));

    for (size_t i = 0; i < dynamicBackends.size(); i++)
    {
        BackendVersion dynamicBackendVersion = dynamicBackends[i]->GetBackendVersion();
        CHECK(TestDynamicBackendUtils::IsBackendCompatible(dynamicBackendVersion));
    }

    // Dummy registry used for testing
    BackendRegistry backendRegistry;
    CHECK(backendRegistry.Size() == 0);

    // Check that no dynamic backend got registered
    BackendIdSet registeredBackendIds = TestDynamicBackendUtils::RegisterDynamicBackendsImplTest(backendRegistry,
                                                                                                 dynamicBackends);
    CHECK(backendRegistry.Size() == 0);
    CHECK(registeredBackendIds.empty());
}

#if !defined(ARMNN_DYNAMIC_BACKEND_ENABLED)

void RuntimeEmptyTestImpl()
{
    using namespace armnn;

    // Swapping the backend registry storage for testing
    TestBackendRegistry testBackendRegistry;

    const BackendRegistry& backendRegistry = BackendRegistryInstance();
    CHECK(backendRegistry.Size() == 0);

    IRuntime::CreationOptions creationOptions;
    IRuntimePtr runtime = IRuntime::Create(creationOptions);

    const DeviceSpec& deviceSpec = *PolymorphicDowncast<const DeviceSpec*>(&runtime->GetDeviceSpec());
    BackendIdSet supportedBackendIds = deviceSpec.GetSupportedBackends();
    CHECK(supportedBackendIds.empty());

    CHECK(backendRegistry.Size() == 0);
}

#endif

void RuntimeDynamicBackendsTestImpl()
{
    using namespace armnn;
    using namespace fs;

    // Swapping the backend registry storage for testing
    TestBackendRegistry testBackendRegistry;

    // This directory contains valid and invalid backends
    std::string testDynamicBackendsSubDir5 = GetTestSubDirectory(g_TestDynamicBackendsSubDir5);
    CHECK(exists(testDynamicBackendsSubDir5));

    // Using the path override in CreationOptions to load some test dynamic backends
    IRuntime::CreationOptions creationOptions;
    creationOptions.m_DynamicBackendsPath = testDynamicBackendsSubDir5;
    IRuntimePtr runtime = IRuntime::Create(creationOptions);

    std::vector<BackendId> expectedRegisteredbackendIds
    {
        "TestValid2",
        "TestValid3"
    };

    const BackendRegistry& backendRegistry = BackendRegistryInstance();
    CHECK(backendRegistry.Size() == expectedRegisteredbackendIds.size());

    BackendIdSet backendIds = backendRegistry.GetBackendIds();
    for (const BackendId& expectedRegisteredbackendId : expectedRegisteredbackendIds)
    {
        CHECK((backendIds.find(expectedRegisteredbackendId) != backendIds.end()));
    }

    const DeviceSpec& deviceSpec = *PolymorphicDowncast<const DeviceSpec*>(&runtime->GetDeviceSpec());
    BackendIdSet supportedBackendIds = deviceSpec.GetSupportedBackends();
    CHECK(supportedBackendIds.size() == expectedRegisteredbackendIds.size());
    for (const BackendId& expectedRegisteredbackendId : expectedRegisteredbackendIds)
    {
        CHECK((supportedBackendIds.find(expectedRegisteredbackendId) != supportedBackendIds.end()));
    }
}

void RuntimeDuplicateDynamicBackendsTestImpl()
{
    using namespace armnn;
    using namespace fs;

    // Swapping the backend registry storage for testing
    TestBackendRegistry testBackendRegistry;

    // This directory contains valid, invalid and duplicate backends
    std::string testDynamicBackendsSubDir6 = GetTestSubDirectory(g_TestDynamicBackendsSubDir6);
    CHECK(exists(testDynamicBackendsSubDir6));

    // Using the path override in CreationOptions to load some test dynamic backends
    IRuntime::CreationOptions creationOptions;
    creationOptions.m_DynamicBackendsPath = testDynamicBackendsSubDir6;
    IRuntimePtr runtime = IRuntime::Create(creationOptions);

    std::vector<BackendId> expectedRegisteredbackendIds
    {
        "TestValid2",
        "TestValid5"
    };

    const BackendRegistry& backendRegistry = BackendRegistryInstance();
    CHECK(backendRegistry.Size() == expectedRegisteredbackendIds.size());

    BackendIdSet backendIds = backendRegistry.GetBackendIds();
    for (const BackendId& expectedRegisteredbackendId : expectedRegisteredbackendIds)
    {
        CHECK((backendIds.find(expectedRegisteredbackendId) != backendIds.end()));
    }

    const DeviceSpec& deviceSpec = *PolymorphicDowncast<const DeviceSpec*>(&runtime->GetDeviceSpec());
    BackendIdSet supportedBackendIds = deviceSpec.GetSupportedBackends();
    CHECK(supportedBackendIds.size() == expectedRegisteredbackendIds.size());
    for (const BackendId& expectedRegisteredbackendId : expectedRegisteredbackendIds)
    {
        CHECK((supportedBackendIds.find(expectedRegisteredbackendId) != supportedBackendIds.end()));
    }
}

void RuntimeInvalidDynamicBackendsTestImpl()
{
    using namespace armnn;
    using namespace fs;

    // Swapping the backend registry storage for testing
    TestBackendRegistry testBackendRegistry;

    // This directory contains only invalid backends
    std::string testDynamicBackendsSubDir9 = GetTestSubDirectory(g_TestDynamicBackendsSubDir9);
    CHECK(exists(testDynamicBackendsSubDir9));

    // Using the path override in CreationOptions to load some test dynamic backends
    IRuntime::CreationOptions creationOptions;
    creationOptions.m_DynamicBackendsPath = testDynamicBackendsSubDir9;
    IRuntimePtr runtime = IRuntime::Create(creationOptions);

    const BackendRegistry& backendRegistry = BackendRegistryInstance();
    CHECK(backendRegistry.Size() == 0);

    const DeviceSpec& deviceSpec = *PolymorphicDowncast<const DeviceSpec*>(&runtime->GetDeviceSpec());
    BackendIdSet supportedBackendIds = deviceSpec.GetSupportedBackends();
    CHECK(supportedBackendIds.empty());
}

void RuntimeInvalidOverridePathTestImpl()
{
    using namespace armnn;

    // Swapping the backend registry storage for testing
    TestBackendRegistry testBackendRegistry;

    // Using the path override in CreationOptions to load some test dynamic backends
    IRuntime::CreationOptions creationOptions;
    creationOptions.m_DynamicBackendsPath = "InvalidPath";
    IRuntimePtr runtime = IRuntime::Create(creationOptions);

    const BackendRegistry& backendRegistry = BackendRegistryInstance();
    CHECK(backendRegistry.Size() == 0);

    const DeviceSpec& deviceSpec = *PolymorphicDowncast<const DeviceSpec*>(&runtime->GetDeviceSpec());
    BackendIdSet supportedBackendIds = deviceSpec.GetSupportedBackends();
    CHECK(supportedBackendIds.empty());
}

#if defined(ARMNNREF_ENABLED)

// This test unit needs the reference backend, it's not available if the reference backend is not built

void CreateReferenceDynamicBackendTestImpl()
{
    using namespace armnn;
    using namespace fs;

    // Swapping the backend registry storage for testing
    TestBackendRegistry testBackendRegistry;

    // This directory contains the reference dynamic backend
    std::string dynamicBackendsBaseDir = GetDynamicBackendsBasePath();
    std::string referenceDynamicBackendSubDir = GetTestSubDirectory(dynamicBackendsBaseDir,
                                                                    g_ReferenceDynamicBackendSubDir);
    CHECK(exists(referenceDynamicBackendSubDir));

    // Check that the reference dynamic backend file exists
    std::string referenceBackendFilePath = GetTestFilePath(referenceDynamicBackendSubDir,
                                                           g_ReferenceBackendFileName);
    CHECK(exists(referenceBackendFilePath));

    // Using the path override in CreationOptions to load the reference dynamic backend
    IRuntime::CreationOptions creationOptions;
    creationOptions.m_DynamicBackendsPath = referenceDynamicBackendSubDir;
    IRuntimePtr runtime = IRuntime::Create(creationOptions);

    const BackendRegistry& backendRegistry = BackendRegistryInstance();
    CHECK(backendRegistry.Size() == 1);

    BackendIdSet backendIds = backendRegistry.GetBackendIds();
    CHECK((backendIds.find("CpuRef") != backendIds.end()));

    const DeviceSpec& deviceSpec = *PolymorphicDowncast<const DeviceSpec*>(&runtime->GetDeviceSpec());
    BackendIdSet supportedBackendIds = deviceSpec.GetSupportedBackends();
    CHECK(supportedBackendIds.size() == 1);
    CHECK((supportedBackendIds.find("CpuRef") != supportedBackendIds.end()));

    // Get the factory function
    auto referenceDynamicBackendFactoryFunction = backendRegistry.GetFactory("CpuRef");
    CHECK((referenceDynamicBackendFactoryFunction != nullptr));

    // Use the factory function to create an instance of the reference backend
    IBackendInternalUniquePtr referenceDynamicBackend = referenceDynamicBackendFactoryFunction();
    CHECK((referenceDynamicBackend != nullptr));
    CHECK((referenceDynamicBackend->GetId() == "CpuRef"));

    // Test the backend instance by querying the layer support
    IBackendInternal::ILayerSupportSharedPtr referenceLayerSupport = referenceDynamicBackend->GetLayerSupport();
    CHECK((referenceLayerSupport != nullptr));

    TensorShape inputShape {  1, 16, 16, 16 };
    TensorShape outputShape{  1, 16, 16, 16 };
    TensorShape weightShape{ 16,  1,  1, 16 };
    TensorInfo inputInfo (inputShape,  DataType::Float32);
    TensorInfo outputInfo(outputShape, DataType::Float32);
    TensorInfo weightInfo(weightShape, DataType::Float32);
    Convolution2dDescriptor convolution2dDescriptor;
    std::vector<TensorInfo> infos = {inputInfo, outputInfo, weightInfo, TensorInfo()};
    bool referenceConvolution2dSupported =
             referenceLayerSupport->IsLayerSupported(LayerType::Convolution2d,
                                                     infos,
                                                     convolution2dDescriptor);
    CHECK(referenceConvolution2dSupported);

    // Test the backend instance by creating a workload
    IBackendInternal::IWorkloadFactoryPtr referenceWorkloadFactory = referenceDynamicBackend->CreateWorkloadFactory();
    CHECK((referenceWorkloadFactory != nullptr));

    // Create dummy settings for the workload
    Convolution2dQueueDescriptor convolution2dQueueDescriptor;
    WorkloadInfo workloadInfo
    {
        { inputInfo, weightInfo },
        { outputInfo },
        "Name",
        EmptyOptional(),
        EmptyOptional(),
        EmptyOptional()
    };
    convolution2dQueueDescriptor.m_Inputs.push_back(nullptr);

    // Create a convolution workload with the dummy settings
    auto workload = referenceWorkloadFactory->CreateWorkload(LayerType::Convolution2d,
                                                             convolution2dQueueDescriptor,
                                                             workloadInfo);
    CHECK((workload != nullptr));
    CHECK(workload.get() == PolymorphicDowncast<RefConvolution2dWorkload*>(workload.get()));
}

#endif

#if defined(SAMPLE_DYNAMIC_BACKEND_ENABLED)

void CheckSampleDynamicBackendLoaded()
{
    using namespace armnn;
    // At this point we expect DYNAMIC_BACKEND_PATHS to include a path to where libArm_SampleDynamic_backend.so is.
    // If it hasn't been loaded there's no point continuing with the rest of the tests.
    BackendIdSet backendIds = BackendRegistryInstance().GetBackendIds();
    if (backendIds.find("SampleDynamic") == backendIds.end())
    {
        std::string message = "The SampleDynamic backend has not been loaded. This may be a build configuration error. "
                              "Ensure a DYNAMIC_BACKEND_PATHS was set at compile time to the location of "
                              "libArm_SampleDynamic_backend.so. "
                              "To disable this test recompile with: -DSAMPLE_DYNAMIC_BACKEND_ENABLED=0";
        FAIL(message);
    }
}

void CreateSampleDynamicBackendTestImpl()
{
    using namespace armnn;
    // Using the path override in CreationOptions to load the reference dynamic backend
    IRuntime::CreationOptions creationOptions;
    IRuntimePtr runtime = IRuntime::Create(creationOptions);
    const BackendRegistry& backendRegistry = BackendRegistryInstance();
    CHECK(backendRegistry.Size() >= 1);
    CheckSampleDynamicBackendLoaded();
    const DeviceSpec& deviceSpec = *PolymorphicDowncast<const DeviceSpec*>(&runtime->GetDeviceSpec());
    BackendIdSet supportedBackendIds = deviceSpec.GetSupportedBackends();
    CHECK(supportedBackendIds.size()>= 1);
    CHECK((supportedBackendIds.find("SampleDynamic") != supportedBackendIds.end()));

    // Get the factory function
    auto sampleDynamicBackendFactoryFunction = backendRegistry.GetFactory("SampleDynamic");
    CHECK((sampleDynamicBackendFactoryFunction != nullptr));

    // Use the factory function to create an instance of the dynamic backend
    IBackendInternalUniquePtr sampleDynamicBackend = sampleDynamicBackendFactoryFunction();
    CHECK((sampleDynamicBackend != nullptr));
    CHECK((sampleDynamicBackend->GetId() == "SampleDynamic"));

    // Test the backend instance by querying the layer support
    IBackendInternal::ILayerSupportSharedPtr sampleLayerSupport = sampleDynamicBackend->GetLayerSupport();
    CHECK((sampleLayerSupport != nullptr));

    TensorShape inputShape {  1, 16, 16, 16 };
    TensorShape outputShape{  1, 16, 16, 16 };
    TensorShape weightShape{ 16,  1,  1, 16 };
    TensorInfo inputInfo (inputShape,  DataType::Float32);
    TensorInfo outputInfo(outputShape, DataType::Float32);
    TensorInfo weightInfo(weightShape, DataType::Float32);
    Convolution2dDescriptor convolution2dDescriptor;
    std::vector<TensorInfo> infos = {inputInfo, outputInfo, weightInfo, TensorInfo()};
    bool sampleConvolution2dSupported =
             sampleLayerSupport->IsLayerSupported(LayerType::Convolution2d,
                                                  infos,
                                                  convolution2dDescriptor);
    CHECK(!sampleConvolution2dSupported);

    // Test the backend instance by creating a workload
    IBackendInternal::IWorkloadFactoryPtr sampleWorkloadFactory = sampleDynamicBackend->CreateWorkloadFactory();
    CHECK((sampleWorkloadFactory != nullptr));

    // Create dummy settings for the workload
    AdditionQueueDescriptor additionQueueDescriptor;
    WorkloadInfo workloadInfo
    {
        { inputInfo, inputInfo },
        { outputInfo },
        "Name",
        EmptyOptional(),
        EmptyOptional(),
        EmptyOptional()
    };

    // Create a addition workload
    auto workload = sampleWorkloadFactory->CreateWorkload(LayerType::Addition, additionQueueDescriptor, workloadInfo);
    CHECK((workload != nullptr));
}

void SampleDynamicBackendEndToEndTestImpl()
{
    using namespace armnn;
    // Create runtime in which test will run
    IRuntime::CreationOptions options;
    IRuntimePtr runtime(IRuntime::Create(options));
    CheckSampleDynamicBackendLoaded();
    // Builds up the structure of the network.
    INetworkPtr net(INetwork::Create());

    IConnectableLayer* input0 = net->AddInputLayer(0);
    IConnectableLayer* input1 = net->AddInputLayer(1);
    ARMNN_NO_DEPRECATE_WARN_BEGIN
    IConnectableLayer* add = net->AddAdditionLayer();
    ARMNN_NO_DEPRECATE_WARN_END
    IConnectableLayer* output = net->AddOutputLayer(0);

    input0->GetOutputSlot(0).Connect(add->GetInputSlot(0));
    input1->GetOutputSlot(0).Connect(add->GetInputSlot(1));
    add->GetOutputSlot(0).Connect(output->GetInputSlot(0));

    TensorInfo tensorInfo(TensorShape({2, 1}), DataType::Float32);
    input0->GetOutputSlot(0).SetTensorInfo(tensorInfo);
    input1->GetOutputSlot(0).SetTensorInfo(tensorInfo);
    add->GetOutputSlot(0).SetTensorInfo(tensorInfo);

    // optimize the network
    IOptimizedNetworkPtr optNet = Optimize(*net, {"SampleDynamic"}, runtime->GetDeviceSpec());

    // Loads it into the runtime.
    NetworkId netId;
    runtime->LoadNetwork(netId, std::move(optNet));

    std::vector<float> input0Data{ 5.0f, 3.0f };
    std::vector<float> input1Data{ 10.0f, 8.0f };
    std::vector<float> expectedOutputData{ 15.0f, 11.0f };
    std::vector<float> outputData(2);

    TensorInfo inputTensorInfo = runtime->GetInputTensorInfo(netId, 0);
    inputTensorInfo.SetConstant(true);
    InputTensors inputTensors
        {
            {0,armnn::ConstTensor(inputTensorInfo, input0Data.data())},
            {1,armnn::ConstTensor(inputTensorInfo, input1Data.data())}
        };
    OutputTensors outputTensors
        {
            {0,armnn::Tensor(runtime->GetOutputTensorInfo(netId, 0), outputData.data())}
        };

    // Does the inference.
    runtime->EnqueueWorkload(netId, inputTensors, outputTensors);

    // Checks the results.
    CHECK(outputData == expectedOutputData);
}
#endif
