/*
 * Copyright (C) 2019-2023 Intel Corporation
 *
 * SPDX-License-Identifier: MIT
 *
 */

#include "shared/source/device_binary_format/patchtokens_decoder.h"
#include "shared/source/helpers/hash.h"
#include "shared/test/common/device_binary_format/patchtokens_tests.h"
#include "shared/test/common/helpers/debug_manager_state_restore.h"
#include "shared/test/common/test_macros/test.h"

#include <vector>

bool hasEmptyHeaps(const NEO::PatchTokenBinary::KernelFromPatchtokens &kernel) {
    return kernel.heaps.generalState.empty() && kernel.heaps.dynamicState.empty() && kernel.heaps.surfaceState.empty();
}

bool hasEmptyTokensInfo(const NEO::PatchTokenBinary::KernelFromPatchtokens &kernel) {
    auto &toks = kernel.tokens;
    bool empty = true;
    empty &= nullptr == toks.samplerStateArray;
    empty &= nullptr == toks.bindingTableState;
    empty &= nullptr == toks.allocateLocalSurface;
    empty &= nullptr == toks.mediaVfeState[0];
    empty &= nullptr == toks.mediaVfeState[1];
    empty &= nullptr == toks.mediaInterfaceDescriptorLoad;
    empty &= nullptr == toks.threadPayload;
    empty &= nullptr == toks.executionEnvironment;
    empty &= nullptr == toks.dataParameterStream;
    empty &= nullptr == toks.kernelAttributesInfo;
    empty &= nullptr == toks.allocateStatelessPrivateSurface;
    empty &= nullptr == toks.allocateStatelessConstantMemorySurfaceWithInitialization;
    empty &= nullptr == toks.allocateStatelessGlobalMemorySurfaceWithInitialization;
    empty &= nullptr == toks.allocateStatelessPrintfSurface;
    empty &= nullptr == toks.allocateStatelessEventPoolSurface;
    empty &= nullptr == toks.allocateStatelessDefaultDeviceQueueSurface;
    empty &= nullptr == toks.inlineVmeSamplerInfo;
    empty &= nullptr == toks.gtpinFreeGrfInfo;
    empty &= nullptr == toks.stateSip;
    empty &= nullptr == toks.allocateSystemThreadSurface;
    empty &= nullptr == toks.gtpinInfo;
    empty &= nullptr == toks.programSymbolTable;
    empty &= nullptr == toks.programRelocationTable;
    empty &= toks.kernelArgs.empty();
    empty &= toks.strings.empty();
    for (int i = 0; i < 3; ++i) {
        empty &= nullptr == toks.crossThreadPayloadArgs.localWorkSize[i];
        empty &= nullptr == toks.crossThreadPayloadArgs.localWorkSize[i];
        empty &= nullptr == toks.crossThreadPayloadArgs.localWorkSize2[i];
        empty &= nullptr == toks.crossThreadPayloadArgs.enqueuedLocalWorkSize[i];
        empty &= nullptr == toks.crossThreadPayloadArgs.numWorkGroups[i];
        empty &= nullptr == toks.crossThreadPayloadArgs.globalWorkOffset[i];
        empty &= nullptr == toks.crossThreadPayloadArgs.globalWorkSize[i];
    }
    empty &= nullptr == toks.crossThreadPayloadArgs.maxWorkGroupSize;
    empty &= nullptr == toks.crossThreadPayloadArgs.workDimensions;
    empty &= nullptr == toks.crossThreadPayloadArgs.simdSize;
    empty &= nullptr == toks.crossThreadPayloadArgs.parentEvent;
    empty &= nullptr == toks.crossThreadPayloadArgs.privateMemoryStatelessSize;
    empty &= nullptr == toks.crossThreadPayloadArgs.localMemoryStatelessWindowSize;
    empty &= nullptr == toks.crossThreadPayloadArgs.localMemoryStatelessWindowStartAddress;
    empty &= nullptr == toks.crossThreadPayloadArgs.preferredWorkgroupMultiple;
    return empty;
}

bool hasEmptyTokensInfo(const NEO::PatchTokenBinary::ProgramFromPatchtokens &program) {
    auto &toks = program.programScopeTokens;
    bool empty = true;
    empty &= toks.allocateConstantMemorySurface.empty();
    empty &= toks.allocateGlobalMemorySurface.empty();
    empty &= toks.constantPointer.empty();
    empty &= toks.globalPointer.empty();
    empty &= nullptr == toks.symbolTable;
    return empty;
}

template <typename TokenT>
uint32_t pushBackToken(iOpenCL::PATCH_TOKEN token, std::vector<uint8_t> &storage) {
    auto offset = storage.size();
    TokenT tok = PatchTokensTestData::initToken<TokenT>(token);
    storage.insert(storage.end(), reinterpret_cast<uint8_t *>(&tok), reinterpret_cast<uint8_t *>((&tok) + 1));
    return static_cast<uint32_t>(offset);
}

template <typename TokenT>
uint32_t pushBackToken(const TokenT &token, std::vector<uint8_t> &storage) {
    auto offset = storage.size();
    storage.insert(storage.end(), reinterpret_cast<const uint8_t *>(&token), reinterpret_cast<const uint8_t *>(&token) + token.Size);
    return static_cast<uint32_t>(offset);
}

uint32_t pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_TOKEN type, std::vector<uint8_t> &storage, uint32_t sourceIndex = 0, uint32_t argNum = 0) {
    iOpenCL::SPatchDataParameterBuffer tok = PatchTokensTestData::initDataParameterBufferToken(type, sourceIndex, argNum);

    auto offset = storage.size();
    storage.insert(storage.end(), reinterpret_cast<const uint8_t *>(&tok), reinterpret_cast<const uint8_t *>(&tok) + tok.Size);
    return static_cast<uint32_t>(offset);
}

bool tokenOffsetMatched(const uint8_t *base, size_t tokenOffset, const iOpenCL::SPatchItemHeader *expectedToken) {
    return (base + tokenOffset) == reinterpret_cast<const uint8_t *>(expectedToken);
}

TEST(GetInlineData, GivenConstantMemorySurfaceTokenThenReturnProperOffsetToInlineData) {
    iOpenCL::SPatchAllocateConstantMemorySurfaceProgramBinaryInfo surfTok[2] = {};
    EXPECT_EQ(reinterpret_cast<uint8_t *>(&surfTok[1]), NEO::PatchTokenBinary::getInlineData(&surfTok[0]));
}

TEST(GetInlineData, GivenGlobalMemorySurfaceTokenThenReturnProperOffsetToInlineData) {
    iOpenCL::SPatchAllocateGlobalMemorySurfaceProgramBinaryInfo surfTok[2] = {};
    EXPECT_EQ(reinterpret_cast<uint8_t *>(&surfTok[1]), NEO::PatchTokenBinary::getInlineData(&surfTok[0]));
}

TEST(GetInlineData, GivenStringTokenThenReturnProperOffsetToInlineData) {
    iOpenCL::SPatchString surfTok[2] = {};
    EXPECT_EQ(reinterpret_cast<uint8_t *>(&surfTok[1]), NEO::PatchTokenBinary::getInlineData(&surfTok[0]));
}

TEST(GetInlineData, GivenKernelArgumentInfoTokenThenReturnDecodedInlineData) {
    std::vector<uint8_t> storage;
    std::string addressQualifier = "__global";
    std::string accessQualifier = "read_write";
    std::string argName = "custom_arg";
    std::string typeName = "int*;";
    std::string typeQualifier = "const";

    PatchTokensTestData::pushBackArgInfoToken(storage, 0U, addressQualifier, accessQualifier, argName, typeName, typeQualifier);

    auto inlineData = NEO::PatchTokenBinary::getInlineData(reinterpret_cast<iOpenCL::SPatchKernelArgumentInfo *>(storage.data()));
    EXPECT_STREQ(addressQualifier.c_str(), std::string(inlineData.addressQualifier.begin(), inlineData.addressQualifier.end()).c_str());
    EXPECT_STREQ(accessQualifier.c_str(), std::string(inlineData.accessQualifier.begin(), inlineData.accessQualifier.end()).c_str());
    EXPECT_STREQ(argName.c_str(), std::string(inlineData.argName.begin(), inlineData.argName.end()).c_str());
    EXPECT_STREQ(typeName.c_str(), std::string(inlineData.typeName.begin(), inlineData.typeName.end()).c_str());
    EXPECT_STREQ(typeQualifier.c_str(), std::string(inlineData.typeQualifiers.begin(), inlineData.typeQualifiers.end()).c_str());
}

TEST(GetInlineData, GivenKernelArgumentInfoTokenWhenNotEnoughDataThenArrayIsBoundsProtected) {
    iOpenCL::SPatchKernelArgumentInfo tokInline = {};
    tokInline.AddressQualifierSize = 4;
    tokInline.AccessQualifierSize = 8;
    tokInline.ArgumentNameSize = 32;
    tokInline.TypeNameSize = 16;
    tokInline.TypeQualifierSize = 6;
    tokInline.Size = sizeof(iOpenCL::SPatchKernelArgumentInfo);
    auto inlineData = NEO::PatchTokenBinary::getInlineData(&tokInline);
    EXPECT_EQ(0U, inlineData.addressQualifier.size());
    EXPECT_EQ(0U, inlineData.accessQualifier.size());
    EXPECT_EQ(0U, inlineData.argName.size());
    EXPECT_EQ(0U, inlineData.typeName.size());
    EXPECT_EQ(0U, inlineData.typeQualifiers.size());
}

TEST(KernelChecksum, GivenKernelBlobThenChecksumIsCalculatedBasedOnDataAfterKernelHeader) {
    std::vector<uint8_t> storage;
    auto kernel = PatchTokensTestData::ValidEmptyKernel::create(storage);
    auto calculatedChecksum = NEO::PatchTokenBinary::calcKernelChecksum(kernel.blobs.kernelInfo);

    auto dataToHash = ArrayRef<const uint8_t>(ptrOffset(storage.data(), sizeof(iOpenCL::SKernelBinaryHeaderCommon)), ptrOffset(storage.data(), storage.size()));
    uint64_t hashValue = NEO::Hash::hash(reinterpret_cast<const char *>(dataToHash.begin()), dataToHash.size());
    uint32_t expectedChecksum = hashValue & 0xFFFFFFFF;
    EXPECT_EQ(expectedChecksum, calculatedChecksum);
}

TEST(KernelChecksum, GivenKernelWithProperChecksumThenValidationSucceeds) {
    std::vector<uint8_t> storage;
    auto kernel = PatchTokensTestData::ValidEmptyKernel::create(storage);
    auto calculatedChecksum = NEO::PatchTokenBinary::calcKernelChecksum(kernel.blobs.kernelInfo);
    EXPECT_EQ(kernel.header->CheckSum, calculatedChecksum);
    EXPECT_FALSE(NEO::PatchTokenBinary::hasInvalidChecksum(kernel));
}

TEST(KernelChecksum, GivenKernelWithInvalidChecksumThenValidationFails) {
    std::vector<uint8_t> storage;
    auto kernel = PatchTokensTestData::ValidEmptyKernel::create(storage);
    auto calculatedChecksum = NEO::PatchTokenBinary::calcKernelChecksum(kernel.blobs.kernelInfo);
    EXPECT_EQ(kernel.header->CheckSum, calculatedChecksum);

    ASSERT_EQ(storage.data(), kernel.blobs.kernelInfo.begin());
    reinterpret_cast<iOpenCL::SKernelBinaryHeader *>(storage.data())->CheckSum += 1;

    EXPECT_TRUE(NEO::PatchTokenBinary::hasInvalidChecksum(kernel));
}

TEST(KernelDecoder, GivenValidEmptyKernelThenDecodingOfHeaderSucceeds) {
    std::vector<uint8_t> storage;
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);
    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(kernelToEncode.blobs.kernelInfo, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    ASSERT_NE(nullptr, decodedKernel.header);
    EXPECT_EQ(kernelToEncode.header, decodedKernel.header);
    EXPECT_EQ(kernelToEncode.name, decodedKernel.name);
    EXPECT_EQ(kernelToEncode.blobs.kernelInfo, decodedKernel.blobs.kernelInfo);
    EXPECT_EQ(0U, decodedKernel.isa.size());
    EXPECT_TRUE(hasEmptyHeaps(decodedKernel));
    EXPECT_EQ(0U, decodedKernel.unhandledTokens.size());
    EXPECT_FALSE(hasEmptyTokensInfo(decodedKernel));
    EXPECT_NE(nullptr, decodedKernel.tokens.executionEnvironment);
}

TEST(KernelDecoder, GivenEmptyKernelWhenBlobSmallerThanKernelHeaderThenDecodingFails) {
    std::vector<uint8_t> storage;
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);
    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    auto brokenBlob = ArrayRef<const uint8_t>(kernelToEncode.blobs.kernelInfo.begin(),
                                              kernelToEncode.blobs.kernelInfo.begin() + sizeof(iOpenCL::SKernelBinaryHeader) - 1);
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(brokenBlob, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);
}

TEST(KernelDecoder, GivenValidKernelWithHeapsThenDecodingSucceedsAndHeapsAreProperlySet) {
    std::vector<uint8_t> storage;
    storage.reserve(512);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    auto kernelExecEnv = *reinterpret_cast<const iOpenCL::SPatchExecutionEnvironment *>(kernelToEncode.blobs.patchList.begin());
    storage.resize(storage.size() - sizeof(kernelExecEnv));

    size_t isaOffset = storage.size();
    kernelHeader->KernelHeapSize = 16U;
    storage.resize(storage.size() + kernelHeader->KernelHeapSize);

    size_t generalStateHeapOffset = storage.size();
    kernelHeader->GeneralStateHeapSize = 24U;
    storage.resize(storage.size() + kernelHeader->GeneralStateHeapSize);

    size_t dynamicStateHeapOffset = storage.size();
    kernelHeader->DynamicStateHeapSize = 8U;
    storage.resize(storage.size() + kernelHeader->DynamicStateHeapSize);

    size_t surfaceStateHeapOffset = storage.size();
    kernelHeader->SurfaceStateHeapSize = 32U;
    storage.resize(storage.size() + kernelHeader->SurfaceStateHeapSize);

    // patchlist needs to come after heaps
    storage.insert(storage.end(), reinterpret_cast<uint8_t *>(&kernelExecEnv), reinterpret_cast<uint8_t *>(&kernelExecEnv + 1));

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);

    EXPECT_EQ(kernelToEncode.header, decodedKernel.header);
    EXPECT_EQ(0U, decodedKernel.unhandledTokens.size());
    EXPECT_FALSE(hasEmptyTokensInfo(decodedKernel));
    EXPECT_NE(nullptr, decodedKernel.tokens.executionEnvironment);

    EXPECT_EQ(kernelToEncode.name, decodedKernel.name);
    EXPECT_EQ(ArrayRef<const uint8_t>(storage.data() + isaOffset, kernelHeader->KernelHeapSize), decodedKernel.isa);
    EXPECT_EQ(ArrayRef<const uint8_t>(storage.data() + generalStateHeapOffset, kernelHeader->GeneralStateHeapSize), decodedKernel.heaps.generalState);
    EXPECT_EQ(ArrayRef<const uint8_t>(storage.data() + dynamicStateHeapOffset, kernelHeader->DynamicStateHeapSize), decodedKernel.heaps.dynamicState);
    EXPECT_EQ(ArrayRef<const uint8_t>(storage.data() + surfaceStateHeapOffset, kernelHeader->SurfaceStateHeapSize), decodedKernel.heaps.surfaceState);
}

TEST(KernelDecoder, GivenEmptyKernelWhenBlobDoesntHaveEnoughSpaceForHeaderDataThenDecodingFails) {
    std::vector<uint8_t> storage;
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);
    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    iOpenCL::SKernelBinaryHeaderCommon originalHeader = *kernelToEncode.header;
    uint32_t outOfBoundsSize = static_cast<uint32_t>(storage.size());

    decodedKernel = {};
    kernelHeader->KernelNameSize = outOfBoundsSize;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(kernelToEncode.blobs.kernelInfo, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);
    kernelHeader->KernelNameSize = originalHeader.KernelNameSize;

    kernelHeader->KernelHeapSize = outOfBoundsSize;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(kernelToEncode.blobs.kernelInfo, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);
    kernelHeader->KernelHeapSize = originalHeader.KernelHeapSize;

    kernelHeader->GeneralStateHeapSize = outOfBoundsSize;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(kernelToEncode.blobs.kernelInfo, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);
    kernelHeader->GeneralStateHeapSize = originalHeader.GeneralStateHeapSize;

    kernelHeader->DynamicStateHeapSize = outOfBoundsSize;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(kernelToEncode.blobs.kernelInfo, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);
    kernelHeader->DynamicStateHeapSize = originalHeader.DynamicStateHeapSize;

    kernelHeader->SurfaceStateHeapSize = outOfBoundsSize;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(kernelToEncode.blobs.kernelInfo, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);
    kernelHeader->SurfaceStateHeapSize = originalHeader.SurfaceStateHeapSize;

    kernelHeader->PatchListSize = outOfBoundsSize;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(kernelToEncode.blobs.kernelInfo, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);
    kernelHeader->PatchListSize = originalHeader.PatchListSize;
}

TEST(KernelDecoder, GivenKernelWithValidKernelPatchtokensThenDecodingSucceedsAndTokensAreProperlyAssigned) {
    using namespace iOpenCL;

    std::vector<uint8_t> storage;
    storage.reserve(1024);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());
    auto samplerStateArrayOff = pushBackToken<SPatchSamplerStateArray>(PATCH_TOKEN_SAMPLER_STATE_ARRAY, storage);
    auto bindingTableStateOff = pushBackToken<SPatchBindingTableState>(PATCH_TOKEN_BINDING_TABLE_STATE, storage);
    auto allocateLocalSurfaceOff = pushBackToken<SPatchAllocateLocalSurface>(PATCH_TOKEN_ALLOCATE_LOCAL_SURFACE, storage);
    auto mediaVfeState0Off = pushBackToken<SPatchMediaVFEState>(PATCH_TOKEN_MEDIA_VFE_STATE, storage);
    auto mediaVfeState1Off = pushBackToken<SPatchMediaVFEState>(PATCH_TOKEN_MEDIA_VFE_STATE_SLOT1, storage);
    auto mediaInterfaceDescriptorLoadOff = pushBackToken<SPatchMediaInterfaceDescriptorLoad>(PATCH_TOKEN_MEDIA_INTERFACE_DESCRIPTOR_LOAD, storage);
    auto threadPayloadOff = pushBackToken<SPatchThreadPayload>(PATCH_TOKEN_THREAD_PAYLOAD, storage);
    auto executionEnvironmentOff = pushBackToken<SPatchExecutionEnvironment>(PATCH_TOKEN_EXECUTION_ENVIRONMENT, storage);
    auto kernelAttributesInfoOff = pushBackToken<SPatchKernelAttributesInfo>(PATCH_TOKEN_KERNEL_ATTRIBUTES_INFO, storage);
    auto allocatedStatelessPrivateMemoryOff = pushBackToken<SPatchKernelAttributesInfo>(PATCH_TOKEN_ALLOCATE_STATELESS_PRIVATE_MEMORY, storage);
    auto allocateStatelessConstantMemorySurfaceWithInitializationOff = pushBackToken<SPatchAllocateStatelessConstantMemorySurfaceWithInitialization>(PATCH_TOKEN_ALLOCATE_STATELESS_CONSTANT_MEMORY_SURFACE_WITH_INITIALIZATION, storage);
    auto allocateStatelessGlobalMemorySurfaceWithInitializationOff = pushBackToken<SPatchAllocateStatelessGlobalMemorySurfaceWithInitialization>(PATCH_TOKEN_ALLOCATE_STATELESS_GLOBAL_MEMORY_SURFACE_WITH_INITIALIZATION, storage);
    auto allocateStatelessPrintfSurfaceOff = pushBackToken<SPatchAllocateStatelessPrintfSurface>(PATCH_TOKEN_ALLOCATE_STATELESS_PRINTF_SURFACE, storage);
    auto allocateStatelessEventPoolSurfaceOff = pushBackToken<SPatchAllocateStatelessEventPoolSurface>(PATCH_TOKEN_ALLOCATE_STATELESS_EVENT_POOL_SURFACE, storage);
    auto allocateStatelessDefaultDeviceQueueSurfaceOff = pushBackToken<SPatchAllocateStatelessDefaultDeviceQueueSurface>(PATCH_TOKEN_ALLOCATE_STATELESS_DEFAULT_DEVICE_QUEUE_SURFACE, storage);
    auto inlineVmeSamplerInfoOff = pushBackToken<SPatchInlineVMESamplerInfo>(PATCH_TOKEN_INLINE_VME_SAMPLER_INFO, storage);
    auto gtpinFreeGrfInfoOff = pushBackToken<SPatchGtpinFreeGRFInfo>(PATCH_TOKEN_GTPIN_FREE_GRF_INFO, storage);
    auto gtpinInfoOff = pushBackToken<SPatchItemHeader>(PATCH_TOKEN_GTPIN_INFO, storage);
    auto stateSipOff = pushBackToken<SPatchStateSIP>(PATCH_TOKEN_STATE_SIP, storage);
    auto programSymbolTableOff = pushBackToken<SPatchFunctionTableInfo>(PATCH_TOKEN_PROGRAM_SYMBOL_TABLE, storage);
    auto programRelocationTableOff = pushBackToken<SPatchFunctionTableInfo>(PATCH_TOKEN_PROGRAM_RELOCATION_TABLE, storage);
    auto dataParameterStreamOff = pushBackToken<SPatchDataParameterStream>(PATCH_TOKEN_DATA_PARAMETER_STREAM, storage);

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    EXPECT_TRUE(decodedKernel.unhandledTokens.empty());
    EXPECT_EQ(ptrOffset(storage.data(), patchListOffset), decodedKernel.blobs.patchList.begin());
    EXPECT_EQ(ptrOffset(storage.data(), storage.size()), decodedKernel.blobs.patchList.end());

    auto base = storage.data();
    EXPECT_TRUE(tokenOffsetMatched(base, samplerStateArrayOff, decodedKernel.tokens.samplerStateArray));
    EXPECT_TRUE(tokenOffsetMatched(base, bindingTableStateOff, decodedKernel.tokens.bindingTableState));
    EXPECT_TRUE(tokenOffsetMatched(base, allocateLocalSurfaceOff, decodedKernel.tokens.allocateLocalSurface));
    EXPECT_TRUE(tokenOffsetMatched(base, mediaVfeState0Off, decodedKernel.tokens.mediaVfeState[0]));
    EXPECT_TRUE(tokenOffsetMatched(base, mediaVfeState1Off, decodedKernel.tokens.mediaVfeState[1]));
    EXPECT_TRUE(tokenOffsetMatched(base, mediaInterfaceDescriptorLoadOff, decodedKernel.tokens.mediaInterfaceDescriptorLoad));
    EXPECT_TRUE(tokenOffsetMatched(base, threadPayloadOff, decodedKernel.tokens.threadPayload));
    EXPECT_TRUE(tokenOffsetMatched(base, executionEnvironmentOff, decodedKernel.tokens.executionEnvironment));
    EXPECT_TRUE(tokenOffsetMatched(base, kernelAttributesInfoOff, decodedKernel.tokens.kernelAttributesInfo));
    EXPECT_TRUE(tokenOffsetMatched(base, allocatedStatelessPrivateMemoryOff, decodedKernel.tokens.allocateStatelessPrivateSurface));
    EXPECT_TRUE(tokenOffsetMatched(base, allocateStatelessConstantMemorySurfaceWithInitializationOff, decodedKernel.tokens.allocateStatelessConstantMemorySurfaceWithInitialization));
    EXPECT_TRUE(tokenOffsetMatched(base, allocateStatelessGlobalMemorySurfaceWithInitializationOff, decodedKernel.tokens.allocateStatelessGlobalMemorySurfaceWithInitialization));
    EXPECT_TRUE(tokenOffsetMatched(base, allocateStatelessPrintfSurfaceOff, decodedKernel.tokens.allocateStatelessPrintfSurface));
    EXPECT_TRUE(tokenOffsetMatched(base, allocateStatelessEventPoolSurfaceOff, decodedKernel.tokens.allocateStatelessEventPoolSurface));
    EXPECT_TRUE(tokenOffsetMatched(base, allocateStatelessDefaultDeviceQueueSurfaceOff, decodedKernel.tokens.allocateStatelessDefaultDeviceQueueSurface));
    EXPECT_TRUE(tokenOffsetMatched(base, inlineVmeSamplerInfoOff, decodedKernel.tokens.inlineVmeSamplerInfo));
    EXPECT_TRUE(tokenOffsetMatched(base, gtpinFreeGrfInfoOff, decodedKernel.tokens.gtpinFreeGrfInfo));
    EXPECT_TRUE(tokenOffsetMatched(base, gtpinInfoOff, decodedKernel.tokens.gtpinInfo));
    EXPECT_TRUE(tokenOffsetMatched(base, stateSipOff, decodedKernel.tokens.stateSip));
    EXPECT_TRUE(tokenOffsetMatched(base, programSymbolTableOff, decodedKernel.tokens.programSymbolTable));
    EXPECT_TRUE(tokenOffsetMatched(base, programRelocationTableOff, decodedKernel.tokens.programRelocationTable));
    EXPECT_TRUE(tokenOffsetMatched(base, dataParameterStreamOff, decodedKernel.tokens.dataParameterStream));
}

TEST(KernelDecoder, GivenKernelWithValidStringPatchtokensThenDecodingSucceedsAndTokensAreProperlyAssigned) {
    std::vector<uint8_t> storage;
    storage.reserve(512);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    iOpenCL::SPatchString stringTok = {};
    stringTok.Token = iOpenCL::PATCH_TOKEN::PATCH_TOKEN_STRING;

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());
    auto string1Off = PatchTokensTestData::pushBackStringToken("str1", 1, storage);
    auto string2Off = PatchTokensTestData::pushBackStringToken("str2", 2, storage);
    auto string0Off = PatchTokensTestData::pushBackStringToken("str0", 0, storage);

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    EXPECT_TRUE(decodedKernel.unhandledTokens.empty());

    auto base = storage.data();
    ASSERT_EQ(3U, decodedKernel.tokens.strings.size());
    EXPECT_TRUE(tokenOffsetMatched(base, string0Off, decodedKernel.tokens.strings[0]));
    EXPECT_TRUE(tokenOffsetMatched(base, string1Off, decodedKernel.tokens.strings[1]));
    EXPECT_TRUE(tokenOffsetMatched(base, string2Off, decodedKernel.tokens.strings[2]));
}

TEST(KernelDecoder, GivenKernelWithValidArgInfoPatchtokensThenDecodingSucceedsAndTokensAreProperlyAssigned) {
    std::vector<uint8_t> storage;
    storage.reserve(512);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    iOpenCL::SPatchKernelArgumentInfo argInfoTok = {};
    argInfoTok.Token = iOpenCL::PATCH_TOKEN::PATCH_TOKEN_KERNEL_ARGUMENT_INFO;

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());

    auto arg1Off = static_cast<uint32_t>(storage.size());
    argInfoTok.ArgumentNumber = 1;
    auto additionalDataSize = 8;
    argInfoTok.Size = sizeof(argInfoTok) + additionalDataSize;
    storage.insert(storage.end(), reinterpret_cast<uint8_t *>(&argInfoTok), reinterpret_cast<uint8_t *>((&argInfoTok) + 1));
    storage.resize(storage.size() + additionalDataSize);

    auto arg2Off = static_cast<uint32_t>(storage.size());
    argInfoTok.ArgumentNumber = 2;
    additionalDataSize = 16;
    argInfoTok.Size = sizeof(argInfoTok) + additionalDataSize;
    storage.insert(storage.end(), reinterpret_cast<uint8_t *>(&argInfoTok), reinterpret_cast<uint8_t *>((&argInfoTok) + 1));
    storage.resize(storage.size() + additionalDataSize);

    auto arg0Off = static_cast<uint32_t>(storage.size());
    argInfoTok.ArgumentNumber = 0;
    additionalDataSize = 24;
    argInfoTok.Size = sizeof(argInfoTok) + additionalDataSize;
    storage.insert(storage.end(), reinterpret_cast<uint8_t *>(&argInfoTok), reinterpret_cast<uint8_t *>((&argInfoTok) + 1));
    storage.resize(storage.size() + additionalDataSize);

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    EXPECT_TRUE(decodedKernel.unhandledTokens.empty());

    auto base = storage.data();
    ASSERT_EQ(3U, decodedKernel.tokens.kernelArgs.size());
    EXPECT_TRUE(tokenOffsetMatched(base, arg0Off, decodedKernel.tokens.kernelArgs[0].argInfo));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1Off, decodedKernel.tokens.kernelArgs[1].argInfo));
    EXPECT_TRUE(tokenOffsetMatched(base, arg2Off, decodedKernel.tokens.kernelArgs[2].argInfo));
}

TEST(KernelDecoder, GivenKernelWithValidObjectArgPatchtokensThenDecodingSucceedsAndTokensAreProperlyAssigned) {
    std::vector<uint8_t> storage;
    storage.reserve(512);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());

    iOpenCL::SPatchSamplerKernelArgument samplerTok = {};
    samplerTok.Token = iOpenCL::PATCH_TOKEN::PATCH_TOKEN_SAMPLER_KERNEL_ARGUMENT;
    samplerTok.Size = sizeof(samplerTok);
    samplerTok.ArgumentNumber = 3;

    iOpenCL::SPatchImageMemoryObjectKernelArgument imageTok = {};
    imageTok.Token = iOpenCL::PATCH_TOKEN::PATCH_TOKEN_IMAGE_MEMORY_OBJECT_KERNEL_ARGUMENT;
    imageTok.Size = sizeof(imageTok);
    imageTok.ArgumentNumber = 1;

    iOpenCL::SPatchGlobalMemoryObjectKernelArgument globalMemTok = {};
    globalMemTok.Token = iOpenCL::PATCH_TOKEN::PATCH_TOKEN_GLOBAL_MEMORY_OBJECT_KERNEL_ARGUMENT;
    globalMemTok.Size = sizeof(globalMemTok);
    globalMemTok.ArgumentNumber = 2;

    iOpenCL::SPatchStatelessGlobalMemoryObjectKernelArgument statelessGlobalMemTok = {};
    statelessGlobalMemTok.Token = iOpenCL::PATCH_TOKEN::PATCH_TOKEN_STATELESS_GLOBAL_MEMORY_OBJECT_KERNEL_ARGUMENT;
    statelessGlobalMemTok.Size = sizeof(statelessGlobalMemTok);
    statelessGlobalMemTok.ArgumentNumber = 0;

    iOpenCL::SPatchStatelessConstantMemoryObjectKernelArgument statelessConstantMemTok = {};
    statelessConstantMemTok.Token = iOpenCL::PATCH_TOKEN::PATCH_TOKEN_STATELESS_CONSTANT_MEMORY_OBJECT_KERNEL_ARGUMENT;
    statelessConstantMemTok.Size = sizeof(statelessConstantMemTok);
    statelessConstantMemTok.ArgumentNumber = 5;

    iOpenCL::SPatchStatelessDeviceQueueKernelArgument statelessDeviceQueueTok = {};
    statelessDeviceQueueTok.Token = iOpenCL::PATCH_TOKEN::PATCH_TOKEN_STATELESS_DEVICE_QUEUE_KERNEL_ARGUMENT;
    statelessDeviceQueueTok.Size = sizeof(iOpenCL::SPatchGlobalMemoryObjectKernelArgument);
    statelessDeviceQueueTok.ArgumentNumber = 4;

    auto samplerOff = pushBackToken(samplerTok, storage);
    auto imageOff = pushBackToken(imageTok, storage);
    auto globalMemOff = pushBackToken(globalMemTok, storage);
    auto statelessGlobalMemOff = pushBackToken(statelessGlobalMemTok, storage);
    auto statelessConstantMemOff = pushBackToken(statelessConstantMemTok, storage);
    auto statelessDeviceQueueOff = pushBackToken(statelessDeviceQueueTok, storage);

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    EXPECT_TRUE(decodedKernel.unhandledTokens.empty());

    auto base = storage.data();
    ASSERT_EQ(6U, decodedKernel.tokens.kernelArgs.size());
    EXPECT_TRUE(tokenOffsetMatched(base, samplerOff, decodedKernel.tokens.kernelArgs[samplerTok.ArgumentNumber].objectArg));
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectType::sampler, decodedKernel.tokens.kernelArgs[samplerTok.ArgumentNumber].objectType);
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectTypeSpecialized::none, decodedKernel.tokens.kernelArgs[samplerTok.ArgumentNumber].objectTypeSpecialized);

    EXPECT_TRUE(tokenOffsetMatched(base, imageOff, decodedKernel.tokens.kernelArgs[imageTok.ArgumentNumber].objectArg));
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectType::image, decodedKernel.tokens.kernelArgs[imageTok.ArgumentNumber].objectType);
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectTypeSpecialized::none, decodedKernel.tokens.kernelArgs[imageTok.ArgumentNumber].objectTypeSpecialized);

    EXPECT_TRUE(tokenOffsetMatched(base, globalMemOff, decodedKernel.tokens.kernelArgs[globalMemTok.ArgumentNumber].objectArg));
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectType::buffer, decodedKernel.tokens.kernelArgs[globalMemTok.ArgumentNumber].objectType);
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectTypeSpecialized::none, decodedKernel.tokens.kernelArgs[globalMemTok.ArgumentNumber].objectTypeSpecialized);

    EXPECT_TRUE(tokenOffsetMatched(base, statelessGlobalMemOff, decodedKernel.tokens.kernelArgs[statelessGlobalMemTok.ArgumentNumber].objectArg));
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectType::buffer, decodedKernel.tokens.kernelArgs[statelessGlobalMemTok.ArgumentNumber].objectType);
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectTypeSpecialized::none, decodedKernel.tokens.kernelArgs[statelessGlobalMemTok.ArgumentNumber].objectTypeSpecialized);

    EXPECT_TRUE(tokenOffsetMatched(base, statelessConstantMemOff, decodedKernel.tokens.kernelArgs[statelessConstantMemTok.ArgumentNumber].objectArg));
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectType::buffer, decodedKernel.tokens.kernelArgs[statelessConstantMemTok.ArgumentNumber].objectType);
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectTypeSpecialized::none, decodedKernel.tokens.kernelArgs[statelessConstantMemTok.ArgumentNumber].objectTypeSpecialized);

    EXPECT_TRUE(tokenOffsetMatched(base, statelessDeviceQueueOff, decodedKernel.tokens.kernelArgs[statelessDeviceQueueTok.ArgumentNumber].objectArg));
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectType::buffer, decodedKernel.tokens.kernelArgs[statelessDeviceQueueTok.ArgumentNumber].objectType);
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectTypeSpecialized::none, decodedKernel.tokens.kernelArgs[statelessDeviceQueueTok.ArgumentNumber].objectTypeSpecialized);

    for (int i = 0; i < 6; ++i) {
        EXPECT_EQ(nullptr, decodedKernel.tokens.kernelArgs[i].argInfo);
        EXPECT_EQ(0U, decodedKernel.tokens.kernelArgs[i].byValMap.size());
        EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectTypeSpecialized::none, decodedKernel.tokens.kernelArgs[i].objectTypeSpecialized);
    }
}

TEST(KernelDecoder, GivenKernelWithValidNonArgCrossThreadDataPatchtokensThenDecodingSucceedsAndTokensAreProperlyAssigned) {
    using namespace iOpenCL;

    std::vector<uint8_t> storage;
    storage.reserve(2048);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());
    auto localWorkSize0Off = pushBackDataParameterToken(DATA_PARAMETER_LOCAL_WORK_SIZE, storage, 0U);
    auto localWorkSize20Off = pushBackDataParameterToken(DATA_PARAMETER_LOCAL_WORK_SIZE, storage, 0U);
    auto localWorkSize1Off = pushBackDataParameterToken(DATA_PARAMETER_LOCAL_WORK_SIZE, storage, 1U);
    auto localWorkSize2Off = pushBackDataParameterToken(DATA_PARAMETER_LOCAL_WORK_SIZE, storage, 2U);
    auto localWorkSize21Off = pushBackDataParameterToken(DATA_PARAMETER_LOCAL_WORK_SIZE, storage, 1U);
    auto localWorkSize22Off = pushBackDataParameterToken(DATA_PARAMETER_LOCAL_WORK_SIZE, storage, 2U);
    auto globalWorkOffset0Off = pushBackDataParameterToken(DATA_PARAMETER_GLOBAL_WORK_OFFSET, storage, 0U);
    auto globalWorkOffset1Off = pushBackDataParameterToken(DATA_PARAMETER_GLOBAL_WORK_OFFSET, storage, 1U);
    auto globalWorkOffset2Off = pushBackDataParameterToken(DATA_PARAMETER_GLOBAL_WORK_OFFSET, storage, 2U);
    auto enqueuedLocalWorkSize0Off = pushBackDataParameterToken(DATA_PARAMETER_ENQUEUED_LOCAL_WORK_SIZE, storage, 0U);
    auto enqueuedLocalWorkSize1Off = pushBackDataParameterToken(DATA_PARAMETER_ENQUEUED_LOCAL_WORK_SIZE, storage, 1U);
    auto enqueuedLocalWorkSize2Off = pushBackDataParameterToken(DATA_PARAMETER_ENQUEUED_LOCAL_WORK_SIZE, storage, 2U);
    auto globalWorkSize0Off = pushBackDataParameterToken(DATA_PARAMETER_GLOBAL_WORK_SIZE, storage, 0U);
    auto globalWorkSize1Off = pushBackDataParameterToken(DATA_PARAMETER_GLOBAL_WORK_SIZE, storage, 1U);
    auto globalWorkSize2Off = pushBackDataParameterToken(DATA_PARAMETER_GLOBAL_WORK_SIZE, storage, 2U);
    auto numWorkGroups0Off = pushBackDataParameterToken(DATA_PARAMETER_NUM_WORK_GROUPS, storage, 0U);
    auto numWorkGroups1Off = pushBackDataParameterToken(DATA_PARAMETER_NUM_WORK_GROUPS, storage, 1U);
    auto numWorkGroups2Off = pushBackDataParameterToken(DATA_PARAMETER_NUM_WORK_GROUPS, storage, 2U);
    auto maxWorkGroupsOff = pushBackDataParameterToken(DATA_PARAMETER_MAX_WORKGROUP_SIZE, storage);
    auto workDimensionsOff = pushBackDataParameterToken(DATA_PARAMETER_WORK_DIMENSIONS, storage);
    auto simdSizeOff = pushBackDataParameterToken(DATA_PARAMETER_SIMD_SIZE, storage);
    auto privateMemoryStatelessSizeOff = pushBackDataParameterToken(DATA_PARAMETER_PRIVATE_MEMORY_STATELESS_SIZE, storage);
    auto localMemoryStatelessWindowSizeOff = pushBackDataParameterToken(DATA_PARAMETER_LOCAL_MEMORY_STATELESS_WINDOW_SIZE, storage);
    auto localMemoryStatelessWindowStartAddrOff = pushBackDataParameterToken(DATA_PARAMETER_LOCAL_MEMORY_STATELESS_WINDOW_START_ADDRESS, storage);
    auto parentEventOff = pushBackDataParameterToken(DATA_PARAMETER_PARENT_EVENT, storage);
    auto preferredWorkgroupMultipleOff = pushBackDataParameterToken(DATA_PARAMETER_PREFERRED_WORKGROUP_MULTIPLE, storage);
    auto implictArgBufferOffset = pushBackDataParameterToken(DATA_PARAMETER_IMPL_ARG_BUFFER, storage);

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    EXPECT_TRUE(decodedKernel.unhandledTokens.empty());

    auto base = storage.data();
    EXPECT_TRUE(tokenOffsetMatched(base, localWorkSize0Off, decodedKernel.tokens.crossThreadPayloadArgs.localWorkSize[0]));
    EXPECT_TRUE(tokenOffsetMatched(base, localWorkSize20Off, decodedKernel.tokens.crossThreadPayloadArgs.localWorkSize2[0]));
    EXPECT_TRUE(tokenOffsetMatched(base, localWorkSize1Off, decodedKernel.tokens.crossThreadPayloadArgs.localWorkSize[1]));
    EXPECT_TRUE(tokenOffsetMatched(base, localWorkSize2Off, decodedKernel.tokens.crossThreadPayloadArgs.localWorkSize[2]));
    EXPECT_TRUE(tokenOffsetMatched(base, localWorkSize21Off, decodedKernel.tokens.crossThreadPayloadArgs.localWorkSize2[1]));
    EXPECT_TRUE(tokenOffsetMatched(base, localWorkSize22Off, decodedKernel.tokens.crossThreadPayloadArgs.localWorkSize2[2]));
    EXPECT_TRUE(tokenOffsetMatched(base, globalWorkOffset0Off, decodedKernel.tokens.crossThreadPayloadArgs.globalWorkOffset[0]));
    EXPECT_TRUE(tokenOffsetMatched(base, globalWorkOffset1Off, decodedKernel.tokens.crossThreadPayloadArgs.globalWorkOffset[1]));
    EXPECT_TRUE(tokenOffsetMatched(base, globalWorkOffset2Off, decodedKernel.tokens.crossThreadPayloadArgs.globalWorkOffset[2]));
    EXPECT_TRUE(tokenOffsetMatched(base, enqueuedLocalWorkSize0Off, decodedKernel.tokens.crossThreadPayloadArgs.enqueuedLocalWorkSize[0]));
    EXPECT_TRUE(tokenOffsetMatched(base, enqueuedLocalWorkSize1Off, decodedKernel.tokens.crossThreadPayloadArgs.enqueuedLocalWorkSize[1]));
    EXPECT_TRUE(tokenOffsetMatched(base, enqueuedLocalWorkSize2Off, decodedKernel.tokens.crossThreadPayloadArgs.enqueuedLocalWorkSize[2]));
    EXPECT_TRUE(tokenOffsetMatched(base, globalWorkSize0Off, decodedKernel.tokens.crossThreadPayloadArgs.globalWorkSize[0]));
    EXPECT_TRUE(tokenOffsetMatched(base, globalWorkSize1Off, decodedKernel.tokens.crossThreadPayloadArgs.globalWorkSize[1]));
    EXPECT_TRUE(tokenOffsetMatched(base, globalWorkSize2Off, decodedKernel.tokens.crossThreadPayloadArgs.globalWorkSize[2]));
    EXPECT_TRUE(tokenOffsetMatched(base, numWorkGroups0Off, decodedKernel.tokens.crossThreadPayloadArgs.numWorkGroups[0]));
    EXPECT_TRUE(tokenOffsetMatched(base, numWorkGroups1Off, decodedKernel.tokens.crossThreadPayloadArgs.numWorkGroups[1]));
    EXPECT_TRUE(tokenOffsetMatched(base, numWorkGroups2Off, decodedKernel.tokens.crossThreadPayloadArgs.numWorkGroups[2]));
    EXPECT_TRUE(tokenOffsetMatched(base, maxWorkGroupsOff, decodedKernel.tokens.crossThreadPayloadArgs.maxWorkGroupSize));
    EXPECT_TRUE(tokenOffsetMatched(base, workDimensionsOff, decodedKernel.tokens.crossThreadPayloadArgs.workDimensions));
    EXPECT_TRUE(tokenOffsetMatched(base, simdSizeOff, decodedKernel.tokens.crossThreadPayloadArgs.simdSize));
    EXPECT_TRUE(tokenOffsetMatched(base, privateMemoryStatelessSizeOff, decodedKernel.tokens.crossThreadPayloadArgs.privateMemoryStatelessSize));
    EXPECT_TRUE(tokenOffsetMatched(base, localMemoryStatelessWindowSizeOff, decodedKernel.tokens.crossThreadPayloadArgs.localMemoryStatelessWindowSize));
    EXPECT_TRUE(tokenOffsetMatched(base, localMemoryStatelessWindowStartAddrOff, decodedKernel.tokens.crossThreadPayloadArgs.localMemoryStatelessWindowStartAddress));
    EXPECT_TRUE(tokenOffsetMatched(base, parentEventOff, decodedKernel.tokens.crossThreadPayloadArgs.parentEvent));
    EXPECT_TRUE(tokenOffsetMatched(base, preferredWorkgroupMultipleOff, decodedKernel.tokens.crossThreadPayloadArgs.preferredWorkgroupMultiple));
    EXPECT_TRUE(tokenOffsetMatched(base, implictArgBufferOffset, decodedKernel.tokens.crossThreadPayloadArgs.implicitArgsBufferOffset));
}

TEST(KernelDecoder, GivenKernelWithArgCrossThreadDataPatchtokensWhenSourceIndexIsGreaterThan2ThenThenDecodingSucceedsButTokenIsMarkedAsUnhandled) {
    std::vector<uint8_t> storage;
    storage.reserve(128);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());

    auto localWorkSize3Off = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_LOCAL_WORK_SIZE, storage, 3U);
    auto globalWorkOffset3Off = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_GLOBAL_WORK_OFFSET, storage, 3U);
    auto enqueuedLocalWorkSize3Off = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_ENQUEUED_LOCAL_WORK_SIZE, storage, 3U);
    auto globalWorkSize3Off = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_GLOBAL_WORK_SIZE, storage, 3U);
    auto numWorkGroups3Off = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_NUM_WORK_GROUPS, storage, 3U);
    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    ASSERT_EQ(5U, decodedKernel.unhandledTokens.size());

    auto base = storage.data();
    EXPECT_TRUE(tokenOffsetMatched(base, localWorkSize3Off, decodedKernel.unhandledTokens[0]));
    EXPECT_TRUE(tokenOffsetMatched(base, globalWorkOffset3Off, decodedKernel.unhandledTokens[1]));
    EXPECT_TRUE(tokenOffsetMatched(base, enqueuedLocalWorkSize3Off, decodedKernel.unhandledTokens[2]));
    EXPECT_TRUE(tokenOffsetMatched(base, globalWorkSize3Off, decodedKernel.unhandledTokens[3]));
    EXPECT_TRUE(tokenOffsetMatched(base, numWorkGroups3Off, decodedKernel.unhandledTokens[4]));
}

TEST(KernelDecoder, GivenKernelWithUnkownPatchtokensThenDecodingSucceedsButTokenIsMarkedAsUnhandled) {
    std::vector<uint8_t> storage;
    storage.reserve(128);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());
    auto unknownTokOff = pushBackToken<iOpenCL::SPatchItemHeader>(iOpenCL::NUM_PATCH_TOKENS, storage);
    auto unknownCrossThreadTokOff = pushBackDataParameterToken(iOpenCL::NUM_DATA_PARAMETER_TOKENS, storage);
    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    ASSERT_EQ(2U, decodedKernel.unhandledTokens.size());

    auto base = storage.data();
    EXPECT_TRUE(tokenOffsetMatched(base, unknownTokOff, decodedKernel.unhandledTokens[0]));
    EXPECT_TRUE(tokenOffsetMatched(base, unknownCrossThreadTokOff, decodedKernel.unhandledTokens[1]));
}

TEST(KernelDecoder, GivenKernelWithValidObjectArgMetadataPatchtokensThenDecodingSucceedsAndTokensAreProperlyAssigned) {
    std::vector<uint8_t> storage;
    storage.reserve(1024);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());

    auto arg0BufferOffsetOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_BUFFER_OFFSET, storage, 0U, 0U);
    auto arg0BufferStatefulOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_BUFFER_STATEFUL, storage, 0U, 0U);

    auto arg1ImageWidthOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_IMAGE_WIDTH, storage, 0U, 1U);
    auto arg1ImageHeightOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_IMAGE_HEIGHT, storage, 0U, 1U);
    auto arg1ImageDepthOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_IMAGE_DEPTH, storage, 0U, 1U);
    auto arg1ImageChannelDataTypeOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_IMAGE_CHANNEL_DATA_TYPE, storage, 0U, 1U);
    auto arg1ImageChannelOrderOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_IMAGE_CHANNEL_ORDER, storage, 0U, 1U);
    auto arg1ImageArraySizeOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_IMAGE_ARRAY_SIZE, storage, 0U, 1U);
    auto arg1ImageNumSamplesOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_IMAGE_NUM_SAMPLES, storage, 0U, 1U);
    auto arg1ImageNumMipLevelOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_IMAGE_NUM_MIP_LEVELS, storage, 0U, 1U);
    auto arg1FlatImageBaseOffsetOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_FLAT_IMAGE_BASEOFFSET, storage, 0U, 1U);
    auto arg1FlatImageWidthOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_FLAT_IMAGE_WIDTH, storage, 0U, 1U);
    auto arg1FlatImageHeightOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_FLAT_IMAGE_HEIGHT, storage, 0U, 1U);
    auto arg1FlatImagePitchOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_FLAT_IMAGE_PITCH, storage, 0U, 1U);

    auto arg2SamplerCoordinateSnapWaRequiredOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_SAMPLER_COORDINATE_SNAP_WA_REQUIRED, storage, 0U, 2U);
    auto arg2SamplerAddressModeOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_SAMPLER_ADDRESS_MODE, storage, 0U, 2U);
    auto arg2SamplerNormalizedCoordsOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_SAMPLER_NORMALIZED_COORDS, storage, 0U, 2U);

    auto arg3SlmTokenOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_SUM_OF_LOCAL_MEMORY_OBJECT_ARGUMENT_SIZES, storage, 0U, 3U);

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    EXPECT_TRUE(decodedKernel.unhandledTokens.empty());

    ASSERT_EQ(4U, decodedKernel.tokens.kernelArgs.size());
    ASSERT_EQ(NEO::PatchTokenBinary::ArgObjectType::buffer, decodedKernel.tokens.kernelArgs[0].objectType);
    ASSERT_EQ(NEO::PatchTokenBinary::ArgObjectType::image, decodedKernel.tokens.kernelArgs[1].objectType);
    ASSERT_EQ(NEO::PatchTokenBinary::ArgObjectType::sampler, decodedKernel.tokens.kernelArgs[2].objectType);
    ASSERT_EQ(NEO::PatchTokenBinary::ArgObjectType::slm, decodedKernel.tokens.kernelArgs[3].objectType);

    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectTypeSpecialized::none, decodedKernel.tokens.kernelArgs[0].objectTypeSpecialized);
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectTypeSpecialized::none, decodedKernel.tokens.kernelArgs[1].objectTypeSpecialized);
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectTypeSpecialized::none, decodedKernel.tokens.kernelArgs[2].objectTypeSpecialized);
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectTypeSpecialized::none, decodedKernel.tokens.kernelArgs[3].objectTypeSpecialized);

    auto base = storage.data();
    EXPECT_TRUE(tokenOffsetMatched(base, arg0BufferOffsetOff, decodedKernel.tokens.kernelArgs[0].metadata.buffer.bufferOffset));
    EXPECT_TRUE(tokenOffsetMatched(base, arg0BufferStatefulOff, decodedKernel.tokens.kernelArgs[0].metadata.buffer.pureStateful));

    EXPECT_TRUE(tokenOffsetMatched(base, arg1ImageWidthOff, decodedKernel.tokens.kernelArgs[1].metadata.image.width));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1ImageHeightOff, decodedKernel.tokens.kernelArgs[1].metadata.image.height));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1ImageDepthOff, decodedKernel.tokens.kernelArgs[1].metadata.image.depth));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1ImageChannelDataTypeOff, decodedKernel.tokens.kernelArgs[1].metadata.image.channelDataType));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1ImageChannelOrderOff, decodedKernel.tokens.kernelArgs[1].metadata.image.channelOrder));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1ImageArraySizeOff, decodedKernel.tokens.kernelArgs[1].metadata.image.arraySize));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1ImageNumSamplesOff, decodedKernel.tokens.kernelArgs[1].metadata.image.numSamples));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1ImageNumMipLevelOff, decodedKernel.tokens.kernelArgs[1].metadata.image.numMipLevels));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1FlatImageBaseOffsetOff, decodedKernel.tokens.kernelArgs[1].metadata.image.flatBaseOffset));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1FlatImageWidthOff, decodedKernel.tokens.kernelArgs[1].metadata.image.flatWidth));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1FlatImageHeightOff, decodedKernel.tokens.kernelArgs[1].metadata.image.flatHeight));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1FlatImagePitchOff, decodedKernel.tokens.kernelArgs[1].metadata.image.flatPitch));

    EXPECT_TRUE(tokenOffsetMatched(base, arg2SamplerCoordinateSnapWaRequiredOff, decodedKernel.tokens.kernelArgs[2].metadata.sampler.coordinateSnapWaRequired));
    EXPECT_TRUE(tokenOffsetMatched(base, arg2SamplerAddressModeOff, decodedKernel.tokens.kernelArgs[2].metadata.sampler.addressMode));
    EXPECT_TRUE(tokenOffsetMatched(base, arg2SamplerNormalizedCoordsOff, decodedKernel.tokens.kernelArgs[2].metadata.sampler.normalizedCoords));

    EXPECT_TRUE(tokenOffsetMatched(base, arg3SlmTokenOff, decodedKernel.tokens.kernelArgs[3].metadata.slm.token));
}

TEST(KernelDecoder, GivenKernelWithMismatchedArgMetadataPatchtokensThenDecodingFails) {
    std::vector<uint8_t> storage;
    storage.reserve(128);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());

    auto arg0Metadata0Off = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_BUFFER_STATEFUL, storage, 0U, 0U);
    auto arg0Metadata1Off = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_BUFFER_OFFSET, storage, 0U, 0U);

    iOpenCL::SPatchDataParameterBuffer *arg0Metadata0 = reinterpret_cast<iOpenCL::SPatchDataParameterBuffer *>(storage.data() + arg0Metadata0Off);
    iOpenCL::SPatchDataParameterBuffer *arg0Metadata1 = reinterpret_cast<iOpenCL::SPatchDataParameterBuffer *>(storage.data() + arg0Metadata1Off);

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);

    decodedKernel = {};
    arg0Metadata0->Type = iOpenCL::DATA_PARAMETER_BUFFER_STATEFUL;
    arg0Metadata1->Type = iOpenCL::DATA_PARAMETER_IMAGE_WIDTH;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);
    EXPECT_TRUE(decodedKernel.unhandledTokens.empty());

    decodedKernel = {};
    arg0Metadata0->Type = iOpenCL::DATA_PARAMETER_BUFFER_STATEFUL;
    arg0Metadata1->Type = iOpenCL::DATA_PARAMETER_SAMPLER_ADDRESS_MODE;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);

    decodedKernel = {};
    arg0Metadata0->Type = iOpenCL::DATA_PARAMETER_BUFFER_STATEFUL;
    arg0Metadata1->Type = iOpenCL::DATA_PARAMETER_SUM_OF_LOCAL_MEMORY_OBJECT_ARGUMENT_SIZES;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);

    decodedKernel = {};
    arg0Metadata0->Type = iOpenCL::DATA_PARAMETER_IMAGE_WIDTH;
    arg0Metadata1->Type = iOpenCL::DATA_PARAMETER_BUFFER_STATEFUL;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);

    decodedKernel = {};
    arg0Metadata0->Type = iOpenCL::DATA_PARAMETER_IMAGE_WIDTH;
    arg0Metadata1->Type = iOpenCL::DATA_PARAMETER_SAMPLER_ADDRESS_MODE;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);

    decodedKernel = {};
    arg0Metadata0->Type = iOpenCL::DATA_PARAMETER_IMAGE_WIDTH;
    arg0Metadata1->Type = iOpenCL::DATA_PARAMETER_SUM_OF_LOCAL_MEMORY_OBJECT_ARGUMENT_SIZES;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);

    decodedKernel = {};
    arg0Metadata0->Type = iOpenCL::DATA_PARAMETER_SAMPLER_ADDRESS_MODE;
    arg0Metadata1->Type = iOpenCL::DATA_PARAMETER_BUFFER_STATEFUL;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);

    decodedKernel = {};
    arg0Metadata0->Type = iOpenCL::DATA_PARAMETER_SAMPLER_ADDRESS_MODE;
    arg0Metadata1->Type = iOpenCL::DATA_PARAMETER_IMAGE_WIDTH;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);

    decodedKernel = {};
    arg0Metadata0->Type = iOpenCL::DATA_PARAMETER_SAMPLER_ADDRESS_MODE;
    arg0Metadata1->Type = iOpenCL::DATA_PARAMETER_SUM_OF_LOCAL_MEMORY_OBJECT_ARGUMENT_SIZES;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);

    decodedKernel = {};
    arg0Metadata0->Type = iOpenCL::DATA_PARAMETER_SUM_OF_LOCAL_MEMORY_OBJECT_ARGUMENT_SIZES;
    arg0Metadata1->Type = iOpenCL::DATA_PARAMETER_IMAGE_WIDTH;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);

    decodedKernel = {};
    arg0Metadata0->Type = iOpenCL::DATA_PARAMETER_SUM_OF_LOCAL_MEMORY_OBJECT_ARGUMENT_SIZES;
    arg0Metadata1->Type = iOpenCL::DATA_PARAMETER_SAMPLER_ADDRESS_MODE;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);

    decodedKernel = {};
    arg0Metadata0->Type = iOpenCL::DATA_PARAMETER_SUM_OF_LOCAL_MEMORY_OBJECT_ARGUMENT_SIZES;
    arg0Metadata1->Type = iOpenCL::DATA_PARAMETER_BUFFER_STATEFUL;
    decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);
}

TEST(KernelDecoder, GivenKernelWithMismatchedArgMetadataPatchtokensThenDecodingFailsAndStops) {
    std::vector<uint8_t> storage;
    storage.reserve(128);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());

    pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_BUFFER_STATEFUL, storage, 0U, 0U);
    pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_IMAGE_DEPTH, storage, 0U, 0U);
    auto unhandledTokenAfterInvalidOff = pushBackDataParameterToken(iOpenCL::NUM_DATA_PARAMETER_TOKENS, storage, 0U, 0U);
    (void)unhandledTokenAfterInvalidOff;

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);
    EXPECT_TRUE(decodedKernel.unhandledTokens.empty());
}

TEST(KernelDecoder, GivenKernelWithByValArgMetadataPatchtokensThenDecodingSucceedsAndTokensAreProperlyAssigned) {
    std::vector<uint8_t> storage;
    storage.reserve(128);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());

    auto arg0Val0Off = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_KERNEL_ARGUMENT, storage, 0U, 0U);
    auto arg0Val1Off = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_KERNEL_ARGUMENT, storage, 0U, 0U);
    auto arg1SlmOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_SUM_OF_LOCAL_MEMORY_OBJECT_ARGUMENT_SIZES, storage, 0U, 1U);

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    EXPECT_TRUE(decodedKernel.unhandledTokens.empty());

    ASSERT_EQ(2U, decodedKernel.tokens.kernelArgs.size());
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectType::none, decodedKernel.tokens.kernelArgs[0].objectType);
    ASSERT_EQ(NEO::PatchTokenBinary::ArgObjectType::slm, decodedKernel.tokens.kernelArgs[1].objectType);

    ASSERT_EQ(2U, decodedKernel.tokens.kernelArgs[0].byValMap.size());
    ASSERT_EQ(1U, decodedKernel.tokens.kernelArgs[1].byValMap.size());
    auto base = storage.data();
    EXPECT_TRUE(tokenOffsetMatched(base, arg0Val0Off, decodedKernel.tokens.kernelArgs[0].byValMap[0]));
    EXPECT_TRUE(tokenOffsetMatched(base, arg0Val1Off, decodedKernel.tokens.kernelArgs[0].byValMap[1]));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1SlmOff, decodedKernel.tokens.kernelArgs[1].metadata.slm.token));
    EXPECT_TRUE(tokenOffsetMatched(base, arg1SlmOff, decodedKernel.tokens.kernelArgs[1].byValMap[0]));
}

TEST(KernelDecoder, GivenKernelWithVmeMetadataPatchtokensThenDecodingSucceedsAndTokensAreProperlyAssigned) {
    std::vector<uint8_t> storage;
    storage.reserve(128);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());

    auto arg0VmeBlockTypeOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_VME_MB_BLOCK_TYPE, storage, 0U, 0U);
    auto arg0VmeSubpixelModeOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_VME_SUBPIXEL_MODE, storage, 0U, 0U);
    auto arg0VmeSadAdjustModeOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_VME_SAD_ADJUST_MODE, storage, 0U, 0U);
    auto arg0VmeSearchPathTypeOff = pushBackDataParameterToken(iOpenCL::DATA_PARAMETER_VME_SEARCH_PATH_TYPE, storage, 0U, 0U);

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    EXPECT_TRUE(decodedKernel.unhandledTokens.empty());

    ASSERT_EQ(1U, decodedKernel.tokens.kernelArgs.size());
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectType::none, decodedKernel.tokens.kernelArgs[0].objectType);
    EXPECT_EQ(NEO::PatchTokenBinary::ArgObjectTypeSpecialized::vme, decodedKernel.tokens.kernelArgs[0].objectTypeSpecialized);
    ;

    auto base = storage.data();
    EXPECT_TRUE(tokenOffsetMatched(base, arg0VmeBlockTypeOff, decodedKernel.tokens.kernelArgs[0].metadataSpecialized.vme.mbBlockType));
    EXPECT_TRUE(tokenOffsetMatched(base, arg0VmeSubpixelModeOff, decodedKernel.tokens.kernelArgs[0].metadataSpecialized.vme.subpixelMode));
    EXPECT_TRUE(tokenOffsetMatched(base, arg0VmeSadAdjustModeOff, decodedKernel.tokens.kernelArgs[0].metadataSpecialized.vme.sadAdjustMode));
    EXPECT_TRUE(tokenOffsetMatched(base, arg0VmeSearchPathTypeOff, decodedKernel.tokens.kernelArgs[0].metadataSpecialized.vme.searchPathType));
}

TEST(KernelDecoder, GivenKernelWithOutOfBoundsTokenThenDecodingFails) {
    std::vector<uint8_t> storage;
    storage.reserve(128);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());
    pushBackToken<iOpenCL::SPatchSamplerStateArray>(iOpenCL::PATCH_TOKEN_SAMPLER_STATE_ARRAY, storage);

    ASSERT_EQ(storage.data(), kernelToEncode.blobs.kernelInfo.begin());
    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);
    kernelHeader->PatchListSize -= 1;

    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedKernel.decodeStatus);
}

TEST(ProgramDecoder, GivenValidEmptyProgramThenDecodingOfHeaderSucceeds) {
    std::vector<uint8_t> storage;
    PatchTokensTestData::ValidEmptyProgram programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedProgram.decodeStatus);
    EXPECT_TRUE(decodedProgram.unhandledTokens.empty());
    ASSERT_NE(nullptr, decodedProgram.header);
    EXPECT_EQ(programToEncode.header, decodedProgram.header);
    EXPECT_EQ(programToEncode.blobs.programInfo, decodedProgram.blobs.programInfo);
    EXPECT_TRUE(decodedProgram.blobs.kernelsInfo.empty());
    EXPECT_TRUE(decodedProgram.blobs.patchList.empty());
    EXPECT_TRUE(decodedProgram.kernels.empty());
    EXPECT_TRUE(hasEmptyTokensInfo(decodedProgram));
}

TEST(ProgramDecoder, GivenProgramWhenBlobSmallerThanProgramHeaderThenDecodingFails) {
    PatchTokensTestData::ValidEmptyProgram programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    auto brokenBlob = ArrayRef<const uint8_t>(programToEncode.blobs.programInfo.begin(),
                                              programToEncode.blobs.programInfo.begin() + sizeof(iOpenCL::SProgramBinaryHeader) - 1);
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(brokenBlob, decodedProgram);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedProgram.decodeStatus);
}

TEST(ProgramDecoder, GivenProgramWithInvaidProgramMagicThenDecodingFails) {
    PatchTokensTestData::ValidEmptyProgram programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    programToEncode.headerMutable->Magic += 1;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedProgram.decodeStatus);
}

TEST(ProgramDecoder, GivenProgramWhenBlobDoesntHaveEnoughSpaceForPatchListThenDecodingFails) {
    PatchTokensTestData::ValidEmptyProgram programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    programToEncode.headerMutable->PatchListSize = static_cast<uint32_t>(programToEncode.blobs.patchList.size() + 1);
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedProgram.decodeStatus);
}

TEST(ProgramDecoder, GivenValidProgramWithConstantSurfacesThenDecodingSucceedsAndTokensAreProperlyAssigned) {
    PatchTokensTestData::ValidProgramWithConstantSurface programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedProgram.decodeStatus);
    EXPECT_TRUE(decodedProgram.unhandledTokens.empty());
    ASSERT_EQ(1U, decodedProgram.programScopeTokens.allocateConstantMemorySurface.size());
    EXPECT_EQ(programToEncode.programScopeTokens.allocateConstantMemorySurface[0], decodedProgram.programScopeTokens.allocateConstantMemorySurface[0]);

    decodedProgram = {};
    auto inlineSize = programToEncode.programScopeTokens.allocateConstantMemorySurface[0]->InlineDataSize;
    auto secondConstantSurfaceOff = programToEncode.storage.size();

    std::vector<uint8_t> copiedConstSurfMutable(reinterpret_cast<uint8_t *>(programToEncode.constSurfMutable),
                                                reinterpret_cast<uint8_t *>(programToEncode.constSurfMutable + 1));
    programToEncode.storage.insert(programToEncode.storage.end(), copiedConstSurfMutable.begin(), copiedConstSurfMutable.end());

    programToEncode.storage.resize(programToEncode.storage.size() + inlineSize);
    programToEncode.recalcTokPtr();
    decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedProgram.decodeStatus);
    EXPECT_TRUE(decodedProgram.unhandledTokens.empty());

    auto base = programToEncode.storage.data();
    ASSERT_EQ(2U, decodedProgram.programScopeTokens.allocateConstantMemorySurface.size());
    EXPECT_EQ(programToEncode.programScopeTokens.allocateConstantMemorySurface[0], decodedProgram.programScopeTokens.allocateConstantMemorySurface[0]);
    EXPECT_TRUE(tokenOffsetMatched(base, secondConstantSurfaceOff, decodedProgram.programScopeTokens.allocateConstantMemorySurface[1]));
}

TEST(ProgramDecoder, GivenProgramWithConstantSurfaceWhenBlobSmallerThanNeededForPatchTokenThenDecodingFails) {
    PatchTokensTestData::ValidProgramWithConstantSurface programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    programToEncode.headerMutable->PatchListSize -= programToEncode.constSurfMutable->InlineDataSize + 1;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedProgram.decodeStatus);
}

TEST(ProgramDecoder, GivenProgramWithConstantSurfaceWhenBlobSmallerThanNeededForInlineDataThenDecodingFails) {
    PatchTokensTestData::ValidProgramWithConstantSurface programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    programToEncode.headerMutable->PatchListSize -= 1;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedProgram.decodeStatus);
}

TEST(ProgramDecoder, GivenValidProgramWithGlobalSurfacesThenDecodingSucceedsAndTokensAreProperlyAssigned) {
    PatchTokensTestData::ValidProgramWithGlobalSurface programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedProgram.decodeStatus);
    EXPECT_TRUE(decodedProgram.unhandledTokens.empty());
    ASSERT_EQ(1U, decodedProgram.programScopeTokens.allocateGlobalMemorySurface.size());
    EXPECT_EQ(programToEncode.programScopeTokens.allocateGlobalMemorySurface[0], decodedProgram.programScopeTokens.allocateGlobalMemorySurface[0]);

    decodedProgram = {};
    auto inlineSize = programToEncode.programScopeTokens.allocateGlobalMemorySurface[0]->InlineDataSize;
    auto secondGlobalSurfaceOff = programToEncode.storage.size();

    std::vector<uint8_t> copiedGlobalSurfMutable(reinterpret_cast<uint8_t *>(programToEncode.globalSurfMutable),
                                                 reinterpret_cast<uint8_t *>(programToEncode.globalSurfMutable + 1));
    programToEncode.storage.insert(programToEncode.storage.end(), copiedGlobalSurfMutable.begin(), copiedGlobalSurfMutable.end());

    programToEncode.storage.resize(programToEncode.storage.size() + inlineSize);
    programToEncode.recalcTokPtr();
    decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedProgram.decodeStatus);
    EXPECT_TRUE(decodedProgram.unhandledTokens.empty());

    auto base = programToEncode.storage.data();
    ASSERT_EQ(2U, decodedProgram.programScopeTokens.allocateGlobalMemorySurface.size());
    EXPECT_EQ(programToEncode.programScopeTokens.allocateGlobalMemorySurface[0], decodedProgram.programScopeTokens.allocateGlobalMemorySurface[0]);
    EXPECT_TRUE(tokenOffsetMatched(base, secondGlobalSurfaceOff, decodedProgram.programScopeTokens.allocateGlobalMemorySurface[1]));
}

TEST(ProgramDecoder, GivenProgramWithGlobalSurfaceWhenBlobSmallerThanNeededForPatchTokenThenDecodingFails) {
    PatchTokensTestData::ValidProgramWithGlobalSurface programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    programToEncode.headerMutable->PatchListSize -= programToEncode.globalSurfMutable->InlineDataSize + 1;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedProgram.decodeStatus);
}

TEST(ProgramDecoder, GivenProgramWithGlobalSurfaceWhenBlobSmallerThanNeededForInlineDataThenDecodingFails) {
    PatchTokensTestData::ValidProgramWithGlobalSurface programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    programToEncode.headerMutable->PatchListSize -= 1;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedProgram.decodeStatus);
}

TEST(ProgramDecoder, GivenValidProgramWithPatchtokensThenDecodingSucceedsAndTokensAreProperlyAssinged) {
    using namespace iOpenCL;

    PatchTokensTestData::ValidProgramWithConstantSurfaceAndPointer programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    auto constPointer1Off = pushBackToken<SPatchConstantPointerProgramBinaryInfo>(PATCH_TOKEN_CONSTANT_POINTER_PROGRAM_BINARY_INFO, programToEncode.storage);
    auto constPointer2Off = pushBackToken<SPatchConstantPointerProgramBinaryInfo>(PATCH_TOKEN_CONSTANT_POINTER_PROGRAM_BINARY_INFO, programToEncode.storage);
    auto globalPointer0Off = pushBackToken<SPatchGlobalPointerProgramBinaryInfo>(PATCH_TOKEN_GLOBAL_POINTER_PROGRAM_BINARY_INFO, programToEncode.storage);
    auto globalPointer1Off = pushBackToken<SPatchGlobalPointerProgramBinaryInfo>(PATCH_TOKEN_GLOBAL_POINTER_PROGRAM_BINARY_INFO, programToEncode.storage);
    auto symbolTableOff = pushBackToken<SPatchFunctionTableInfo>(PATCH_TOKEN_PROGRAM_SYMBOL_TABLE, programToEncode.storage);
    programToEncode.recalcTokPtr();
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedProgram.decodeStatus);
    EXPECT_TRUE(decodedProgram.unhandledTokens.empty());
    auto base = programToEncode.storage.data();

    EXPECT_EQ(1U, programToEncode.programScopeTokens.constantPointer.size());
    ASSERT_EQ(3U, decodedProgram.programScopeTokens.constantPointer.size());
    ASSERT_EQ(2U, decodedProgram.programScopeTokens.globalPointer.size());
    EXPECT_TRUE(tokenOffsetMatched(base, constPointer1Off, decodedProgram.programScopeTokens.constantPointer[1]));
    EXPECT_TRUE(tokenOffsetMatched(base, constPointer2Off, decodedProgram.programScopeTokens.constantPointer[2]));
    EXPECT_TRUE(tokenOffsetMatched(base, globalPointer0Off, decodedProgram.programScopeTokens.globalPointer[0]));
    EXPECT_TRUE(tokenOffsetMatched(base, globalPointer1Off, decodedProgram.programScopeTokens.globalPointer[1]));
    EXPECT_TRUE(tokenOffsetMatched(base, symbolTableOff, decodedProgram.programScopeTokens.symbolTable));
}

TEST(ProgramDecoder, GivenProgramWithUnkownPatchtokensThenDecodingSucceedsButTokenIsMarkedAsUnhandled) {
    PatchTokensTestData::ValidProgramWithConstantSurface programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;

    programToEncode.constSurfMutable->Token = iOpenCL::NUM_PATCH_TOKENS;
    programToEncode.constSurfMutable->Size += programToEncode.constSurfMutable->InlineDataSize;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedProgram.decodeStatus);
    ASSERT_EQ(1U, decodedProgram.unhandledTokens.size());
    EXPECT_EQ(programToEncode.constSurfMutable, decodedProgram.unhandledTokens[0]);
}

TEST(ProgramDecoder, GivenValidProgramWithKernelThenDecodingSucceedsAndTokensAreProperlyAssigned) {
    PatchTokensTestData::ValidProgramWithKernelUsingSlm programToEncode;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.blobs.programInfo, decodedProgram);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedProgram.decodeStatus);
    EXPECT_TRUE(decodedProgram.unhandledTokens.empty());
    ASSERT_EQ(1U, decodedProgram.header->NumberOfKernels);
    ASSERT_EQ(1U, decodedProgram.kernels.size());
    auto decodedKernel = decodedProgram.kernels[0];
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    EXPECT_TRUE(decodedKernel.unhandledTokens.empty());
    EXPECT_NE(nullptr, decodedKernel.tokens.allocateLocalSurface);
}

TEST(ProgramDecoder, GivenValidProgramWithTwoKernelsWhenThenDecodingSucceeds) {
    PatchTokensTestData::ValidProgramWithKernelUsingSlm programToEncode;
    programToEncode.headerMutable->NumberOfKernels = 2;

    std::vector<uint8_t> copiedKernelInfo(programToEncode.kernels[0].blobs.kernelInfo.begin(), programToEncode.kernels[0].blobs.kernelInfo.end());
    programToEncode.storage.insert(programToEncode.storage.end(), copiedKernelInfo.begin(), copiedKernelInfo.end());

    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.storage, decodedProgram);
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedProgram.decodeStatus);
    EXPECT_TRUE(decodedProgram.unhandledTokens.empty());
    EXPECT_EQ(2U, decodedProgram.header->NumberOfKernels);
    ASSERT_EQ(2U, decodedProgram.kernels.size());

    auto decodedKernel0 = decodedProgram.kernels[0];
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel0.decodeStatus);
    EXPECT_TRUE(decodedKernel0.unhandledTokens.empty());
    EXPECT_NE(nullptr, decodedKernel0.tokens.allocateLocalSurface);

    auto decodedKernel1 = decodedProgram.kernels[0];
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel1.decodeStatus);
    EXPECT_TRUE(decodedKernel1.unhandledTokens.empty());
    EXPECT_NE(nullptr, decodedKernel1.tokens.allocateLocalSurface);
}

TEST(ProgramDecoder, GivenPatchTokenWithZeroSizeThenDecodingFailsAndStops) {
    PatchTokensTestData::ValidProgramWithKernelUsingSlm programToEncode;
    programToEncode.slmMutable->Size = 0U;
    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.storage, decodedProgram);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedProgram.decodeStatus);
}

TEST(ProgramDecoder, GivenProgramWithMultipleKernelsWhenFailsToDecodeKernelThenDecodingFailsAndStops) {
    PatchTokensTestData::ValidProgramWithKernelUsingSlm programToEncode;
    programToEncode.slmMutable->Size = 0U;
    programToEncode.headerMutable->NumberOfKernels = 2;

    std::vector<uint8_t> copiedKernelInfo(programToEncode.kernels[0].blobs.kernelInfo.begin(), programToEncode.kernels[0].blobs.kernelInfo.end());
    programToEncode.storage.insert(programToEncode.storage.end(), copiedKernelInfo.begin(), copiedKernelInfo.end());

    NEO::PatchTokenBinary::ProgramFromPatchtokens decodedProgram;
    bool decodeSuccess = NEO::PatchTokenBinary::decodeProgramFromPatchtokensBlob(programToEncode.storage, decodedProgram);
    EXPECT_FALSE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::invalidBinary, decodedProgram.decodeStatus);
    EXPECT_TRUE(decodedProgram.unhandledTokens.empty());
    EXPECT_EQ(2U, decodedProgram.header->NumberOfKernels);
    EXPECT_EQ(1U, decodedProgram.kernels.size());
}

TEST(ProgramDecoder, givenPatchTokenInterfaceDescriptorDataWhenFlagPrintDebugMessagesAndDecodeTokenThenRightMessagePrinted) {
    using namespace iOpenCL;
    DebugManagerStateRestore debugManagerStateRestore;
    debugManager.flags.PrintDebugMessages.set(true);

    std::vector<uint8_t> storage;
    storage.reserve(512);
    auto kernelToEncode = PatchTokensTestData::ValidEmptyKernel::create(storage);

    auto patchListOffset = ptrDiff(kernelToEncode.blobs.patchList.begin(), storage.data());
    pushBackToken<SPatchInterfaceDescriptorData>(PATCH_TOKEN_INTERFACE_DESCRIPTOR_DATA, storage);

    auto kernelHeader = reinterpret_cast<iOpenCL::SKernelBinaryHeaderCommon *>(storage.data());
    kernelHeader->PatchListSize = static_cast<uint32_t>(storage.size() - patchListOffset);
    NEO::PatchTokenBinary::KernelFromPatchtokens decodedKernel;
    testing::internal::CaptureStderr();
    bool decodeSuccess = NEO::PatchTokenBinary::decodeKernelFromPatchtokensBlob(storage, decodedKernel);

    std::string output = testing::internal::GetCapturedStderr();
    EXPECT_TRUE(decodeSuccess);
    EXPECT_EQ(NEO::DecodeError::success, decodedKernel.decodeStatus);
    EXPECT_EQ("Ignored kernel-scope Patch Token: 21\n", output);
}
