/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Debug output (KHR_debug) tests
 *//*--------------------------------------------------------------------*/

#include "es31fDebugTests.hpp"

#include "es31fNegativeTestShared.hpp"
#include "es31fNegativeBufferApiTests.hpp"
#include "es31fNegativeTextureApiTests.hpp"
#include "es31fNegativeShaderApiTests.hpp"
#include "es31fNegativeFragmentApiTests.hpp"
#include "es31fNegativeVertexArrayApiTests.hpp"
#include "es31fNegativeStateApiTests.hpp"
#include "es31fNegativeAtomicCounterTests.hpp"
#include "es31fNegativeShaderImageLoadStoreTests.hpp"
#include "es31fNegativeShaderFunctionTests.hpp"
#include "es31fNegativeShaderDirectiveTests.hpp"
#include "es31fNegativeSSBOBlockTests.hpp"
#include "es31fNegativePreciseTests.hpp"
#include "es31fNegativeAdvancedBlendEquationTests.hpp"
#include "es31fNegativeShaderStorageTests.hpp"
#include "es31fNegativeTessellationTests.hpp"
#include "es31fNegativeComputeTests.hpp"
#include "es31fNegativeSampleVariablesTests.hpp"
#include "es31fNegativeShaderFramebufferFetchTests.hpp"

#include "deUniquePtr.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deSTLUtil.hpp"
#include "deMutex.hpp"
#include "deThread.h"

#include "gluRenderContext.hpp"
#include "gluContextInfo.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluStrUtil.hpp"

#include "glwDefs.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

#include "tes31Context.hpp"
#include "tcuTestContext.hpp"
#include "tcuCommandLine.hpp"
#include "tcuResultCollector.hpp"

#include "glsStateQueryUtil.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{
using namespace glw;

using de::MovePtr;
using std::map;
using std::set;
using std::string;
using std::vector;

using glu::CallLogWrapper;
using tcu::ResultCollector;
using tcu::TestLog;

using NegativeTestShared::NegativeTestContext;

static const GLenum s_debugTypes[] = {
    GL_DEBUG_TYPE_ERROR,       GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR,
    GL_DEBUG_TYPE_PORTABILITY, GL_DEBUG_TYPE_PERFORMANCE,         GL_DEBUG_TYPE_OTHER,
    GL_DEBUG_TYPE_MARKER,      GL_DEBUG_TYPE_PUSH_GROUP,          GL_DEBUG_TYPE_POP_GROUP,
};

static const GLenum s_debugSeverities[] = {
    GL_DEBUG_SEVERITY_HIGH,
    GL_DEBUG_SEVERITY_MEDIUM,
    GL_DEBUG_SEVERITY_LOW,
    GL_DEBUG_SEVERITY_NOTIFICATION,
};

static bool isKHRDebugSupported(Context &ctx)
{
    const bool supportsES32 = glu::contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2));
    return supportsES32 || ctx.getContextInfo().isExtensionSupported("GL_KHR_debug");
}

class BaseCase;

class DebugMessageTestContext : public NegativeTestContext
{
public:
    DebugMessageTestContext(BaseCase &host, glu::RenderContext &renderCtx, const glu::ContextInfo &ctxInfo,
                            tcu::TestLog &log, tcu::ResultCollector &results, bool enableLog);
    ~DebugMessageTestContext(void);

    void expectMessage(GLenum source, GLenum type);

private:
    BaseCase &m_debugHost;
};

class TestFunctionWrapper
{
public:
    typedef void (*CoreTestFunc)(NegativeTestContext &ctx);
    typedef void (*DebugTestFunc)(DebugMessageTestContext &ctx);

    TestFunctionWrapper(void);
    explicit TestFunctionWrapper(CoreTestFunc func);
    explicit TestFunctionWrapper(DebugTestFunc func);

    void call(DebugMessageTestContext &ctx) const;

private:
    enum FuncType
    {
        TYPE_NULL = 0,
        TYPE_CORE,
        TYPE_DEBUG,
    };
    FuncType m_type;

    union
    {
        CoreTestFunc coreFn;
        DebugTestFunc debugFn;
    } m_func;
};

TestFunctionWrapper::TestFunctionWrapper(void) : m_type(TYPE_NULL)
{
    m_func.coreFn = 0;
}

TestFunctionWrapper::TestFunctionWrapper(CoreTestFunc func) : m_type(TYPE_CORE)
{
    m_func.coreFn = func;
}

TestFunctionWrapper::TestFunctionWrapper(DebugTestFunc func) : m_type(TYPE_DEBUG)
{
    m_func.debugFn = func;
}

void TestFunctionWrapper::call(DebugMessageTestContext &ctx) const
{
    if (m_type == TYPE_CORE)
        m_func.coreFn(static_cast<NegativeTestContext &>(ctx));
    else if (m_type == TYPE_DEBUG)
        m_func.debugFn(ctx);
    else
        DE_ASSERT(false);
}

void emitMessages(DebugMessageTestContext &ctx, GLenum source)
{
    for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_debugTypes); typeNdx++)
    {
        for (int severityNdx = 0; severityNdx < DE_LENGTH_OF_ARRAY(s_debugSeverities); severityNdx++)
        {
            const GLenum type     = s_debugTypes[typeNdx];
            const GLenum severity = s_debugSeverities[severityNdx];
            const string msg = string("Application generated message with type ") + glu::getDebugMessageTypeName(type) +
                               " and severity " + glu::getDebugMessageSeverityName(severity);

            // Use severity as ID, guaranteed unique
            ctx.glDebugMessageInsert(source, type, severity, severity, -1, msg.c_str());
            ctx.expectMessage(source, type);
        }
    }
}

void application_messages(DebugMessageTestContext &ctx)
{
    ctx.beginSection("Messages with source of GL_DEBUG_SOURCE_APPLICATION");
    emitMessages(ctx, GL_DEBUG_SOURCE_APPLICATION);
    ctx.endSection();
}

void thirdparty_messages(DebugMessageTestContext &ctx)
{
    ctx.beginSection("Messages with source of GL_DEBUG_SOURCE_THIRD_PARTY");
    emitMessages(ctx, GL_DEBUG_SOURCE_THIRD_PARTY);
    ctx.endSection();
}

void push_pop_messages(DebugMessageTestContext &ctx)
{
    ctx.beginSection("Push/Pop Debug Group");

    ctx.glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Application group 1");
    ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP);
    ctx.glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 2, -1, "Application group 1-1");
    ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP);
    ctx.glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 3, -1, "Application group 1-1-1");
    ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP);
    ctx.glPopDebugGroup();
    ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP);
    ctx.glPopDebugGroup();
    ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP);

    ctx.glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 4, -1, "Application group 1-2");
    ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP);
    ctx.glPopDebugGroup();
    ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP);

    ctx.glPushDebugGroup(GL_DEBUG_SOURCE_THIRD_PARTY, 4, -1, "3rd Party group 1-3");
    ctx.expectMessage(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DEBUG_TYPE_PUSH_GROUP);
    ctx.glPopDebugGroup();
    ctx.expectMessage(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DEBUG_TYPE_POP_GROUP);
    ctx.glPopDebugGroup();
    ctx.expectMessage(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP);

    ctx.glPushDebugGroup(GL_DEBUG_SOURCE_THIRD_PARTY, 4, -1, "3rd Party group 2");
    ctx.expectMessage(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DEBUG_TYPE_PUSH_GROUP);
    ctx.glPopDebugGroup();
    ctx.expectMessage(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DEBUG_TYPE_POP_GROUP);

    ctx.endSection();
}

struct FunctionContainer
{
    TestFunctionWrapper function;
    const char *name;
    const char *desc;
};

vector<FunctionContainer> getUserMessageFuncs(void)
{
    FunctionContainer funcs[] = {
        {TestFunctionWrapper(application_messages), "application_messages",
         "Externally generated messages from the application"},
        {TestFunctionWrapper(thirdparty_messages), "third_party_messages",
         "Externally generated messages from a third party"},
        {TestFunctionWrapper(push_pop_messages), "push_pop_stack", "Messages from pushing/popping debug groups"},
    };

    return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
}

// Data required to uniquely identify a debug message
struct MessageID
{
    GLenum source;
    GLenum type;
    GLuint id;

    MessageID(void) : source(GL_NONE), type(GL_NONE), id(0)
    {
    }
    MessageID(GLenum source_, GLenum type_, GLuint id_) : source(source_), type(type_), id(id_)
    {
    }

    bool operator==(const MessageID &rhs) const
    {
        return source == rhs.source && type == rhs.type && id == rhs.id;
    }
    bool operator!=(const MessageID &rhs) const
    {
        return source != rhs.source || type != rhs.type || id != rhs.id;
    }
    bool operator<(const MessageID &rhs) const
    {
        return source < rhs.source || (source == rhs.source && (type < rhs.type || (type == rhs.type && id < rhs.id)));
    }
};

std::ostream &operator<<(std::ostream &str, const MessageID &id)
{
    return str << glu::getDebugMessageSourceStr(id.source) << ", " << glu::getDebugMessageTypeStr(id.type) << ", "
               << id.id;
}

// All info from a single debug message
struct MessageData
{
    MessageID id;
    GLenum severity;
    string message;

    MessageData(void) : id(MessageID()), severity(GL_NONE)
    {
    }
    MessageData(const MessageID &id_, GLenum severity_, const string &message_)
        : id(id_)
        , severity(severity_)
        , message(message_)
    {
    }
};

extern "C" typedef void GLW_APIENTRY DebugCallbackFunc(GLenum, GLenum, GLuint, GLenum, GLsizei, const char *,
                                                       const void *);

// Base class
class BaseCase : public NegativeTestShared::ErrorCase
{
public:
    BaseCase(Context &ctx, const char *name, const char *desc);
    virtual ~BaseCase(void)
    {
    }

    virtual IterateResult iterate(void) = 0;

    virtual void expectMessage(GLenum source, GLenum type);
    virtual void expectError(GLenum error0, GLenum error1);

protected:
    struct VerificationResult
    {
        const qpTestResult result;
        const string resultMessage;
        const string logMessage;

        VerificationResult(qpTestResult result_, const string &resultMessage_, const string &logMessage_)
            : result(result_)
            , resultMessage(resultMessage_)
            , logMessage(logMessage_)
        {
        }
    };

    static DebugCallbackFunc callbackHandle;
    virtual void callback(GLenum source, GLenum type, GLuint id, GLenum severity, const std::string &message);

    VerificationResult verifyMessageCount(const MessageID &id, GLenum severity, int refCount, int resCount,
                                          bool messageEnabled) const;

    // Verify a single message instance against expected attributes
    void verifyMessage(const MessageData &message, GLenum source, GLenum type, GLuint id, GLenum severity);
    void verifyMessage(const MessageData &message, GLenum source, GLenum type);

    bool verifyMessageExists(const MessageData &message, GLenum source, GLenum type);
    void verifyMessageGroup(const MessageData &message, GLenum source, GLenum type);
    void verifyMessageString(const MessageData &message);

    bool isDebugContext(void) const;

    tcu::ResultCollector m_results;
};

void BaseCase::callbackHandle(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
                              const char *message, const void *userParam)
{
    static_cast<BaseCase *>(const_cast<void *>(userParam))
        ->callback(source, type, id, severity, string(message, &message[length]));
}

BaseCase::BaseCase(Context &ctx, const char *name, const char *desc) : ErrorCase(ctx, name, desc)
{
}

void BaseCase::expectMessage(GLenum source, GLenum type)
{
    DE_UNREF(source);
    DE_UNREF(type);
}

void BaseCase::expectError(GLenum error0, GLenum error1)
{
    if (error0 != GL_NO_ERROR || error1 != GL_NO_ERROR)
        expectMessage(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR);
    else
        expectMessage(GL_DONT_CARE, GL_DONT_CARE);
}

void BaseCase::callback(GLenum source, GLenum type, GLuint id, GLenum severity, const string &message)
{
    DE_UNREF(source);
    DE_UNREF(type);
    DE_UNREF(id);
    DE_UNREF(severity);
    DE_UNREF(message);
}

BaseCase::VerificationResult BaseCase::verifyMessageCount(const MessageID &id, GLenum severity, int refCount,
                                                          int resCount, bool messageEnabled) const
{
    std::stringstream log;

    // This message should not be filtered out
    if (messageEnabled)
    {
        if (resCount != refCount)
        {
            /*
             * Technically nothing requires the implementation to be consistent in terms
             * of the messages it produces in most situations, allowing the set of messages
             * produced to vary between executions. This function splits messages
             * into deterministic and non-deterministic to facilitate handling of such messages.
             *
             * Non-deterministic messages that are present in differing quantities in filtered and
             * unfiltered runs will not fail the test case unless in direct violation of a filter:
             * the implementation may produce an arbitrary number of such messages when they are
             * not filtered out and none when they are filtered.
             *
             * A list of error source/type combinations with their assumed behaviour and
             * the rationale for expecting such behaviour follows
             *
             * For API/shader messages we assume that the following types are deterministic:
             *   DEBUG_TYPE_ERROR                 Errors specified by spec and should always be produced
             *
             * For API messages the following types are assumed to be non-deterministic
             * and treated as quality warnings since the underlying reported issue does not change between calls:
             *   DEBUG_TYPE_DEPRECATED_BEHAVIOR   Reasonable to only report first instance
             *   DEBUG_TYPE_UNDEFINED_BEHAVIOR    Reasonable to only report first instance
             *   DEBUG_TYPE_PORTABILITY           Reasonable to only report first instance
             *
             * For API messages the following types are assumed to be non-deterministic
             * and do not affect test results.
             *   DEBUG_TYPE_PERFORMANCE           May be tied to arbitrary factors, reasonable to report only first instance
             *   DEBUG_TYPE_OTHER                 Definition allows arbitrary contents
             *
             * For 3rd party and application messages the following types are deterministic:
             *   DEBUG_TYPE_MARKER                Only generated by test
             *   DEBUG_TYPE_PUSH_GROUP            Only generated by test
             *   DEBUG_TYPE_POP_GROUP             Only generated by test
             *   All others                       Only generated by test
             *
             * All messages with category of window system or other are treated as non-deterministic
             * and do not effect test results since they can be assumed to be outside control of
             * both the implementation and test case
             *
             */

            const bool isDeterministic =
                id.source == GL_DEBUG_SOURCE_APPLICATION || id.source == GL_DEBUG_SOURCE_THIRD_PARTY ||
                ((id.source == GL_DEBUG_SOURCE_API || id.source == GL_DEBUG_SOURCE_SHADER_COMPILER) &&
                 id.type == GL_DEBUG_TYPE_ERROR);

            const bool canIgnore = id.source == GL_DEBUG_SOURCE_WINDOW_SYSTEM || id.source == GL_DEBUG_SOURCE_OTHER;

            if (isDeterministic)
            {
                if (resCount > refCount)
                {
                    log << "Extra instances of message were found: (" << id << ") with "
                        << glu::getDebugMessageSeverityStr(severity) << " (got " << resCount << ", expected "
                        << refCount << ")";
                    return VerificationResult(QP_TEST_RESULT_FAIL,
                                              "Extra instances of a deterministic message were present", log.str());
                }
                else
                {
                    log << "Instances of message were missing: (" << id << ") with "
                        << glu::getDebugMessageSeverityStr(severity) << " (got " << resCount << ", expected "
                        << refCount << ")";
                    return VerificationResult(QP_TEST_RESULT_FAIL, "Message missing", log.str());
                }
            }
            else if (!canIgnore)
            {
                if (resCount > refCount)
                {
                    log << "Extra instances of message were found but the message is non-deterministic(warning): ("
                        << id << ") with " << glu::getDebugMessageSeverityStr(severity) << " (got " << resCount
                        << ", expected " << refCount << ")";
                    return VerificationResult(QP_TEST_RESULT_QUALITY_WARNING,
                                              "Extra instances of a message were present", log.str());
                }
                else
                {
                    log << "Instances of message were missing but the message is non-deterministic(warning): (" << id
                        << ") with " << glu::getDebugMessageSeverityStr(severity) << " (got " << resCount
                        << ", expected " << refCount << ")";
                    return VerificationResult(QP_TEST_RESULT_QUALITY_WARNING, "Message missing", log.str());
                }
            }
            else
            {
                if (resCount > refCount)
                {
                    log << "Extra instances of message were found but the message is non-deterministic(ignored): ("
                        << id << ") with " << glu::getDebugMessageSeverityStr(severity) << " (got " << resCount
                        << ", expected " << refCount << ")";
                    return VerificationResult(QP_TEST_RESULT_PASS, "", log.str());
                }
                else
                {
                    log << "Instances of message were missing but the message is non-deterministic(ignored): (" << id
                        << ") with " << glu::getDebugMessageSeverityStr(severity) << " (got " << resCount
                        << ", expected " << refCount << ")";
                    return VerificationResult(QP_TEST_RESULT_PASS, "", log.str());
                }
            }
        }
        else // Passed as appropriate
        {
            log << "Message was found when expected: (" << id << ") with " << glu::getDebugMessageSeverityStr(severity);
            return VerificationResult(QP_TEST_RESULT_PASS, "", log.str());
        }
    }
    // Message should be filtered out
    else
    {
        // Filtered out
        if (resCount == 0)
        {
            log << "Message was excluded correctly:  (" << id << ") with " << glu::getDebugMessageSeverityStr(severity);
            return VerificationResult(QP_TEST_RESULT_PASS, "", log.str());
        }
        // Only present in filtered run (ERROR)
        else if (resCount > 0 && refCount == 0)
        {
            log << "A message was not excluded as it should have been: (" << id << ") with "
                << glu::getDebugMessageSeverityStr(severity) << ". This message was not present in the reference run";
            return VerificationResult(QP_TEST_RESULT_FAIL, "A message was not filtered out", log.str());
        }
        // Present in both runs (ERROR)
        else
        {
            log << "A message was not excluded as it should have been: (" << id << ") with "
                << glu::getDebugMessageSeverityStr(severity);
            return VerificationResult(QP_TEST_RESULT_FAIL, "A message was not filtered out", log.str());
        }
    }
}

// Return true if message needs further verification
bool BaseCase::verifyMessageExists(const MessageData &message, GLenum source, GLenum type)
{
    TestLog &log = m_testCtx.getLog();

    if (source == GL_DONT_CARE || type == GL_DONT_CARE)
        return false;
    else if (message.id.source == GL_NONE || message.id.type == GL_NONE)
    {
        if (isDebugContext())
        {
            m_results.addResult(QP_TEST_RESULT_FAIL, "Message was not reported as expected");
            log << TestLog::Message << "A message was expected but none was reported" << TestLog::EndMessage;
        }
        else
        {
            m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING,
                                "Verification accuracy is lacking without a debug context");
            log << TestLog::Message << "A message was expected but none was reported. Running without a debug context"
                << TestLog::EndMessage;
        }
        return false;
    }
    else
        return true;
}

void BaseCase::verifyMessageGroup(const MessageData &message, GLenum source, GLenum type)
{
    TestLog &log = m_testCtx.getLog();

    if (message.id.source != source)
    {
        m_results.addResult(QP_TEST_RESULT_FAIL, "Incorrect message source");
        log << TestLog::Message << "Message source was " << glu::getDebugMessageSourceStr(message.id.source)
            << " when it should have been " << glu::getDebugMessageSourceStr(source) << TestLog::EndMessage;
    }

    if (message.id.type != type)
    {
        m_results.addResult(QP_TEST_RESULT_FAIL, "Incorrect message type");
        log << TestLog::Message << "Message type was " << glu::getDebugMessageTypeStr(message.id.type)
            << " when it should have been " << glu::getDebugMessageTypeStr(type) << TestLog::EndMessage;
    }
}

void BaseCase::verifyMessageString(const MessageData &message)
{
    TestLog &log = m_testCtx.getLog();

    log << TestLog::Message << "Driver says: \"" << message.message << "\"" << TestLog::EndMessage;

    if (message.message.empty())
    {
        m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING, "Empty message");
        log << TestLog::Message << "Message message was empty" << TestLog::EndMessage;
    }
}

void BaseCase::verifyMessage(const MessageData &message, GLenum source, GLenum type)
{
    if (verifyMessageExists(message, source, type))
    {
        verifyMessageString(message);
        verifyMessageGroup(message, source, type);
    }
}

void BaseCase::verifyMessage(const MessageData &message, GLenum source, GLenum type, GLuint id, GLenum severity)
{
    TestLog &log = m_testCtx.getLog();

    if (verifyMessageExists(message, source, type))
    {
        verifyMessageString(message);
        verifyMessageGroup(message, source, type);

        if (message.id.id != id)
        {
            m_results.addResult(QP_TEST_RESULT_FAIL, "Incorrect message id");
            log << TestLog::Message << "Message id was " << message.id.id << " when it should have been " << id
                << TestLog::EndMessage;
        }

        if (message.severity != severity)
        {
            m_results.addResult(QP_TEST_RESULT_FAIL, "Incorrect message severity");
            log << TestLog::Message << "Message severity was " << glu::getDebugMessageSeverityStr(message.severity)
                << " when it should have been " << glu::getDebugMessageSeverityStr(severity) << TestLog::EndMessage;
        }
    }
}

bool BaseCase::isDebugContext(void) const
{
    return (m_context.getRenderContext().getType().getFlags() & glu::CONTEXT_DEBUG) != 0;
}

// Generate errors, verify that each error results in a callback call
class CallbackErrorCase : public BaseCase
{
public:
    CallbackErrorCase(Context &ctx, const char *name, const char *desc, TestFunctionWrapper errorFunc);
    virtual ~CallbackErrorCase(void)
    {
    }

    virtual IterateResult iterate(void);

    virtual void expectMessage(GLenum source, GLenum type);

private:
    virtual void callback(GLenum source, GLenum type, GLuint id, GLenum severity, const string &message);

    const TestFunctionWrapper m_errorFunc;
    MessageData m_lastMessage;
};

CallbackErrorCase::CallbackErrorCase(Context &ctx, const char *name, const char *desc, TestFunctionWrapper errorFunc)
    : BaseCase(ctx, name, desc)
    , m_errorFunc(errorFunc)
{
}

CallbackErrorCase::IterateResult CallbackErrorCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::TestLog &log        = m_testCtx.getLog();
    DebugMessageTestContext context =
        DebugMessageTestContext(*this, m_context.getRenderContext(), m_context.getContextInfo(), log, m_results, true);

    gl.enable(GL_DEBUG_OUTPUT);
    gl.enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    gl.debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, false); // disable all
    gl.debugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, DE_NULL,
                           true); // enable API errors
    gl.debugMessageControl(GL_DEBUG_SOURCE_APPLICATION, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL,
                           true); // enable application messages
    gl.debugMessageControl(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL,
                           true); // enable third party messages
    gl.debugMessageCallback(callbackHandle, this);

    m_errorFunc.call(context);

    gl.debugMessageCallback(DE_NULL, DE_NULL);
    gl.disable(GL_DEBUG_OUTPUT);

    m_results.setTestContextResult(m_testCtx);

    return STOP;
}

void CallbackErrorCase::expectMessage(GLenum source, GLenum type)
{
    verifyMessage(m_lastMessage, source, type);
    m_lastMessage = MessageData();

    // Reset error so that code afterwards (such as glu::ShaderProgram) doesn't break because of
    // lingering error state.
    m_context.getRenderContext().getFunctions().getError();
}

void CallbackErrorCase::callback(GLenum source, GLenum type, GLuint id, GLenum severity, const string &message)
{
    m_lastMessage = MessageData(MessageID(source, type, id), severity, message);
}

// Generate errors, verify that each error results in a log entry
class LogErrorCase : public BaseCase
{
public:
    LogErrorCase(Context &context, const char *name, const char *desc, TestFunctionWrapper errorFunc);
    virtual ~LogErrorCase(void)
    {
    }

    virtual IterateResult iterate(void);

    virtual void expectMessage(GLenum source, GLenum type);

private:
    const TestFunctionWrapper m_errorFunc;
    MessageData m_lastMessage;
};

LogErrorCase::LogErrorCase(Context &ctx, const char *name, const char *desc, TestFunctionWrapper errorFunc)
    : BaseCase(ctx, name, desc)
    , m_errorFunc(errorFunc)
{
}

LogErrorCase::IterateResult LogErrorCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::TestLog &log        = m_testCtx.getLog();
    DebugMessageTestContext context =
        DebugMessageTestContext(*this, m_context.getRenderContext(), m_context.getContextInfo(), log, m_results, true);
    GLint numMsg = 0;

    gl.enable(GL_DEBUG_OUTPUT);
    gl.enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    gl.debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, false); // disable all
    gl.debugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, DE_NULL,
                           true);              // enable API errors
    gl.debugMessageCallback(DE_NULL, DE_NULL); // enable logging
    gl.getIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMsg);
    gl.getDebugMessageLog(numMsg, 0, DE_NULL, DE_NULL, DE_NULL, DE_NULL, DE_NULL, DE_NULL); // clear log

    m_errorFunc.call(context);

    gl.disable(GL_DEBUG_OUTPUT);
    m_results.setTestContextResult(m_testCtx);

    return STOP;
}

void LogErrorCase::expectMessage(GLenum source, GLenum type)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    int numMsg               = 0;
    TestLog &log             = m_testCtx.getLog();
    MessageData lastMsg;

    if (source == GL_DONT_CARE || type == GL_DONT_CARE)
        return;

    gl.getIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMsg);

    if (numMsg == 0)
    {
        if (isDebugContext())
        {
            m_results.addResult(QP_TEST_RESULT_FAIL, "Error was not reported as expected");
            log << TestLog::Message << "A message was expected but none was reported (empty message log)"
                << TestLog::EndMessage;
        }
        else
        {
            m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING,
                                "Verification accuracy is lacking without a debug context");
            log << TestLog::Message
                << "A message was expected but none was reported (empty message log). Running without a debug context"
                << TestLog::EndMessage;
        }
        return;
    }

    // There may be messages other than the error we are looking for in the log.
    // Strictly nothing prevents the implementation from producing more than the
    // required error from an API call with a defined error. however we assume that
    // since calls that produce an error should not change GL state the implementation
    // should have nothing else to report.
    if (numMsg > 1)
        gl.getDebugMessageLog(numMsg - 1, 0, DE_NULL, DE_NULL, DE_NULL, DE_NULL, DE_NULL,
                              DE_NULL); // Clear all but last

    {
        int msgLen = 0;
        gl.getIntegerv(GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH, &msgLen);

        TCU_CHECK_MSG(msgLen >= 0, "Negative message length");
        TCU_CHECK_MSG(msgLen < 100000, "Excessively long message");

        lastMsg.message.resize(msgLen);
        gl.getDebugMessageLog(1, msgLen, &lastMsg.id.source, &lastMsg.id.type, &lastMsg.id.id, &lastMsg.severity,
                              &msgLen, &lastMsg.message[0]);
    }

    log << TestLog::Message << "Driver says: \"" << lastMsg.message << "\"" << TestLog::EndMessage;

    verifyMessage(lastMsg, source, type);

    // Reset error so that code afterwards (such as glu::ShaderProgram) doesn't break because of
    // lingering error state.
    m_context.getRenderContext().getFunctions().getError();
}

// Generate errors, verify that calling glGetError afterwards produces desired result
class GetErrorCase : public BaseCase
{
public:
    GetErrorCase(Context &ctx, const char *name, const char *desc, TestFunctionWrapper errorFunc);
    virtual ~GetErrorCase(void)
    {
    }

    virtual IterateResult iterate(void);

    virtual void expectMessage(GLenum source, GLenum type);
    virtual void expectError(glw::GLenum error0, glw::GLenum error1);

private:
    const TestFunctionWrapper m_errorFunc;
};

GetErrorCase::GetErrorCase(Context &ctx, const char *name, const char *desc, TestFunctionWrapper errorFunc)
    : BaseCase(ctx, name, desc)
    , m_errorFunc(errorFunc)
{
}

GetErrorCase::IterateResult GetErrorCase::iterate(void)
{
    tcu::TestLog &log = m_testCtx.getLog();
    DebugMessageTestContext context =
        DebugMessageTestContext(*this, m_context.getRenderContext(), m_context.getContextInfo(), log, m_results, true);

    m_errorFunc.call(context);

    m_results.setTestContextResult(m_testCtx);

    return STOP;
}

void GetErrorCase::expectMessage(GLenum source, GLenum type)
{
    DE_UNREF(source);
    DE_UNREF(type);
    DE_FATAL("GetErrorCase cannot handle anything other than error codes");
}

void GetErrorCase::expectError(glw::GLenum error0, glw::GLenum error1)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    TestLog &log             = m_testCtx.getLog();

    const GLenum result = gl.getError();

    if (result != error0 && result != error1)
    {
        m_results.addResult(QP_TEST_RESULT_FAIL, "Incorrect error was reported");
        if (error0 == error1)
            log << TestLog::Message << glu::getErrorStr(error0) << " was expected but got " << glu::getErrorStr(result)
                << TestLog::EndMessage;
        else
            log << TestLog::Message << glu::getErrorStr(error0) << " or " << glu::getErrorStr(error1)
                << " was expected but got " << glu::getErrorStr(result) << TestLog::EndMessage;
        return;
    }
}

// Generate errors, log the types, disable some, regenerate errors, verify correct errors (not)reported
class FilterCase : public BaseCase
{
public:
    FilterCase(Context &ctx, const char *name, const char *desc, const vector<TestFunctionWrapper> &errorFuncs);
    virtual ~FilterCase(void)
    {
    }

    virtual IterateResult iterate(void);

    virtual void expectMessage(GLenum source, GLenum type);

protected:
    struct MessageFilter
    {
        MessageFilter() : source(GL_DONT_CARE), type(GL_DONT_CARE), severity(GL_DONT_CARE), enabled(true)
        {
        } // Default to enable all
        MessageFilter(GLenum source_, GLenum type_, GLenum severity_, const vector<GLuint> &ids_, bool enabled_)
            : source(source_)
            , type(type_)
            , severity(severity_)
            , ids(ids_)
            , enabled(enabled_)
        {
        }

        GLenum source;
        GLenum type;
        GLenum severity;
        vector<GLuint> ids;
        bool enabled;
    };

    virtual void callback(GLenum source, GLenum type, GLuint id, GLenum severity, const string &message);

    vector<MessageData> genMessages(bool uselog, const string &desc);

    vector<MessageFilter> genFilters(const vector<MessageData> &messages, const vector<MessageFilter> &initial,
                                     uint32_t seed, int iterations) const;
    void applyFilters(const vector<MessageFilter> &filters) const;
    bool isEnabled(const vector<MessageFilter> &filters, const MessageData &message) const;

    void verify(const vector<MessageData> &refMessages, const vector<MessageData> &filteredMessages,
                const vector<MessageFilter> &filters);

    const vector<TestFunctionWrapper> m_errorFuncs;

    vector<MessageData> *m_currentErrors;
};

FilterCase::FilterCase(Context &ctx, const char *name, const char *desc, const vector<TestFunctionWrapper> &errorFuncs)
    : BaseCase(ctx, name, desc)
    , m_errorFuncs(errorFuncs)
    , m_currentErrors(DE_NULL)
{
}

FilterCase::IterateResult FilterCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    gl.enable(GL_DEBUG_OUTPUT);
    gl.enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    gl.debugMessageCallback(callbackHandle, this);

    try
    {
        gl.debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, true);

        {
            const vector<MessageData> refMessages = genMessages(true, "Reference run");
            const MessageFilter baseFilter(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, vector<GLuint>(), true);
            const uint32_t baseSeed = deStringHash(getName()) ^ m_testCtx.getCommandLine().getBaseSeed();
            const vector<MessageFilter> filters =
                genFilters(refMessages, vector<MessageFilter>(1, baseFilter), baseSeed, 4);
            vector<MessageData> filteredMessages;

            applyFilters(filters);

            // Generate errors
            filteredMessages = genMessages(false, "Filtered run");

            // Verify
            verify(refMessages, filteredMessages, filters);

            if (!isDebugContext() && refMessages.empty())
                m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING,
                                    "Verification accuracy is lacking without a debug context");
        }
    }
    catch (...)
    {
        gl.disable(GL_DEBUG_OUTPUT);
        gl.debugMessageCallback(DE_NULL, DE_NULL);
        throw;
    }

    gl.disable(GL_DEBUG_OUTPUT);
    gl.debugMessageCallback(DE_NULL, DE_NULL);
    m_results.setTestContextResult(m_testCtx);

    return STOP;
}

void FilterCase::expectMessage(GLenum source, GLenum type)
{
    DE_UNREF(source);
    DE_UNREF(type);
}

void FilterCase::callback(GLenum source, GLenum type, GLuint id, GLenum severity, const string &message)
{
    if (m_currentErrors)
        m_currentErrors->push_back(MessageData(MessageID(source, type, id), severity, message));
}

vector<MessageData> FilterCase::genMessages(bool uselog, const string &desc)
{
    tcu::TestLog &log               = m_testCtx.getLog();
    DebugMessageTestContext context = DebugMessageTestContext(*this, m_context.getRenderContext(),
                                                              m_context.getContextInfo(), log, m_results, uselog);
    tcu::ScopedLogSection section(log, "message gen", desc);
    vector<MessageData> messages;

    m_currentErrors = &messages;

    for (int ndx = 0; ndx < int(m_errorFuncs.size()); ndx++)
        m_errorFuncs[ndx].call(context);

    m_currentErrors = DE_NULL;

    return messages;
}

vector<FilterCase::MessageFilter> FilterCase::genFilters(const vector<MessageData> &messages,
                                                         const vector<MessageFilter> &initial, uint32_t seed,
                                                         int iterations) const
{
    de::Random rng(seed ^ deInt32Hash(deStringHash(getName())));

    set<MessageID> tempMessageIds;
    set<GLenum> tempSources;
    set<GLenum> tempTypes;
    set<GLenum> tempSeverities;

    if (messages.empty())
        return initial;

    for (int ndx = 0; ndx < int(messages.size()); ndx++)
    {
        const MessageData &msg = messages[ndx];

        tempMessageIds.insert(msg.id);
        tempSources.insert(msg.id.source);
        tempTypes.insert(msg.id.type);
        tempSeverities.insert(msg.severity);
    }

    {
        // Fetchable by index
        const vector<MessageID> messageIds(tempMessageIds.begin(), tempMessageIds.end());
        const vector<GLenum> sources(tempSources.begin(), tempSources.end());
        const vector<GLenum> types(tempTypes.begin(), tempTypes.end());
        const vector<GLenum> severities(tempSeverities.begin(), tempSeverities.end());

        vector<MessageFilter> filters = initial;

        for (int iteration = 0; iteration < iterations; iteration++)
        {
            switch (rng.getInt(0, 8)) // Distribute so that per-message randomization (the default branch) is prevalent
            {
            case 0:
            {
                const GLenum source = sources[rng.getInt(0, int(sources.size() - 1))];
                const bool enabled  = rng.getBool();

                filters.push_back(MessageFilter(source, GL_DONT_CARE, GL_DONT_CARE, vector<GLuint>(), enabled));
                break;
            }

            case 1:
            {
                const GLenum type  = types[rng.getUint32() % types.size()];
                const bool enabled = rng.getBool();

                filters.push_back(MessageFilter(GL_DONT_CARE, type, GL_DONT_CARE, vector<GLuint>(), enabled));
                break;
            }

            case 2:
            {
                const GLenum severity = severities[rng.getUint32() % severities.size()];
                const bool enabled    = rng.getBool();

                filters.push_back(MessageFilter(GL_DONT_CARE, GL_DONT_CARE, severity, vector<GLuint>(), enabled));
                break;
            }

            default:
            {
                const int start = rng.getInt(0, int(messageIds.size()));

                for (int itr = 0; itr < 4; itr++)
                {
                    const MessageID &id = messageIds[(start + itr) % messageIds.size()];
                    const bool enabled  = rng.getBool();

                    filters.push_back(
                        MessageFilter(id.source, id.type, GL_DONT_CARE, vector<GLuint>(1, id.id), enabled));
                }
            }
            }
        }

        return filters;
    }
}

void FilterCase::applyFilters(const vector<MessageFilter> &filters) const
{
    TestLog &log = m_testCtx.getLog();
    const tcu::ScopedLogSection section(log, "", "Setting message filters");
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    for (size_t filterNdx = 0; filterNdx < filters.size(); filterNdx++)
    {
        const MessageFilter &filter = filters[filterNdx];

        if (filter.ids.empty())
            log << TestLog::Message << "Setting messages with"
                << " source " << glu::getDebugMessageSourceStr(filter.source) << ", type "
                << glu::getDebugMessageTypeStr(filter.type) << " and severity "
                << glu::getDebugMessageSeverityStr(filter.severity) << (filter.enabled ? " to enabled" : " to disabled")
                << TestLog::EndMessage;
        else
        {
            for (size_t ndx = 0; ndx < filter.ids.size(); ndx++)
                log << TestLog::Message << "Setting message (" << MessageID(filter.source, filter.type, filter.ids[ndx])
                    << ") to " << (filter.enabled ? "enabled" : "disabled") << TestLog::EndMessage;
        }

        gl.debugMessageControl(filter.source, filter.type, filter.severity, GLsizei(filter.ids.size()),
                               filter.ids.empty() ? DE_NULL : &filter.ids[0], filter.enabled);
    }
}

bool FilterCase::isEnabled(const vector<MessageFilter> &filters, const MessageData &message) const
{
    bool retval = true;

    for (size_t filterNdx = 0; filterNdx < filters.size(); filterNdx++)
    {
        const MessageFilter &filter = filters[filterNdx];

        if (filter.ids.empty())
        {
            if (filter.source != GL_DONT_CARE && filter.source != message.id.source)
                continue;

            if (filter.type != GL_DONT_CARE && filter.type != message.id.type)
                continue;

            if (filter.severity != GL_DONT_CARE && filter.severity != message.severity)
                continue;
        }
        else
        {
            DE_ASSERT(filter.source != GL_DONT_CARE);
            DE_ASSERT(filter.type != GL_DONT_CARE);
            DE_ASSERT(filter.severity == GL_DONT_CARE);

            if (filter.source != message.id.source || filter.type != message.id.type)
                continue;

            if (!de::contains(filter.ids.begin(), filter.ids.end(), message.id.id))
                continue;
        }

        retval = filter.enabled;
    }

    return retval;
}

struct MessageMeta
{
    int refCount;
    int resCount;
    GLenum severity;

    MessageMeta(void) : refCount(0), resCount(0), severity(GL_NONE)
    {
    }
};

void FilterCase::verify(const vector<MessageData> &refMessages, const vector<MessageData> &resMessages,
                        const vector<MessageFilter> &filters)
{
    TestLog &log = m_testCtx.getLog();
    map<MessageID, MessageMeta> counts;

    log << TestLog::Section("verification", "Verifying");

    // Gather message counts & severities, report severity mismatches if found
    for (size_t refNdx = 0; refNdx < refMessages.size(); refNdx++)
    {
        const MessageData &msg = refMessages[refNdx];
        MessageMeta &meta      = counts[msg.id];

        if (meta.severity != GL_NONE && meta.severity != msg.severity)
        {
            log << TestLog::Message << "A message has variable severity between instances: (" << msg.id
                << ") with severity " << glu::getDebugMessageSeverityStr(meta.severity) << " and "
                << glu::getDebugMessageSeverityStr(msg.severity) << TestLog::EndMessage;
            m_results.addResult(QP_TEST_RESULT_FAIL, "Message severity changed between instances of the same message");
        }

        meta.refCount++;
        meta.severity = msg.severity;
    }

    for (size_t resNdx = 0; resNdx < resMessages.size(); resNdx++)
    {
        const MessageData &msg = resMessages[resNdx];
        MessageMeta &meta      = counts[msg.id];

        if (meta.severity != GL_NONE && meta.severity != msg.severity)
        {
            log << TestLog::Message << "A message has variable severity between instances: (" << msg.id
                << ") with severity " << glu::getDebugMessageSeverityStr(meta.severity) << " and "
                << glu::getDebugMessageSeverityStr(msg.severity) << TestLog::EndMessage;
            m_results.addResult(QP_TEST_RESULT_FAIL, "Message severity changed between instances of the same message");
        }

        meta.resCount++;
        meta.severity = msg.severity;
    }

    for (map<MessageID, MessageMeta>::const_iterator itr = counts.begin(); itr != counts.end(); itr++)
    {
        const MessageID &id   = itr->first;
        const GLenum severity = itr->second.severity;

        const int refCount = itr->second.refCount;
        const int resCount = itr->second.resCount;
        const bool enabled = isEnabled(filters, MessageData(id, severity, ""));

        VerificationResult result = verifyMessageCount(id, severity, refCount, resCount, enabled);

        log << TestLog::Message << result.logMessage << TestLog::EndMessage;

        if (result.result != QP_TEST_RESULT_PASS)
            m_results.addResult(result.result, result.resultMessage);
    }

    log << TestLog::EndSection;
}

// Filter case that uses debug groups
class GroupFilterCase : public FilterCase
{
public:
    GroupFilterCase(Context &ctx, const char *name, const char *desc, const vector<TestFunctionWrapper> &errorFuncs);
    virtual ~GroupFilterCase(void)
    {
    }

    virtual IterateResult iterate(void);
};

GroupFilterCase::GroupFilterCase(Context &ctx, const char *name, const char *desc,
                                 const vector<TestFunctionWrapper> &errorFuncs)
    : FilterCase(ctx, name, desc, errorFuncs)
{
}

template <typename T>
vector<T> join(const vector<T> &a, const vector<T> &b)
{
    vector<T> retval;

    retval.reserve(a.size() + b.size());
    retval.insert(retval.end(), a.begin(), a.end());
    retval.insert(retval.end(), b.begin(), b.end());
    return retval;
}

GroupFilterCase::IterateResult GroupFilterCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::TestLog &log        = m_testCtx.getLog();

    gl.enable(GL_DEBUG_OUTPUT);
    gl.enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    gl.debugMessageCallback(callbackHandle, this);

    try
    {
        gl.debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, true);

        {

            // Generate reference (all errors)
            const vector<MessageData> refMessages = genMessages(true, "Reference run");
            const uint32_t baseSeed               = deStringHash(getName()) ^ m_testCtx.getCommandLine().getBaseSeed();
            const MessageFilter baseFilter(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, vector<GLuint>(), true);
            const vector<MessageFilter> filter0 =
                genFilters(refMessages, vector<MessageFilter>(1, baseFilter), baseSeed, 4);
            vector<MessageData> resMessages0;

            applyFilters(filter0);

            resMessages0 = genMessages(false, "Filtered run, default debug group");

            // Initial verification
            verify(refMessages, resMessages0, filter0);

            {
                // Generate reference (filters inherited from parent)
                const vector<MessageFilter> filter1base =
                    genFilters(refMessages, vector<MessageFilter>(), baseSeed ^ 0xDEADBEEF, 4);
                const vector<MessageFilter> filter1full = join(filter0, filter1base);
                tcu::ScopedLogSection section1(log, "", "Pushing Debug Group");
                vector<MessageData> resMessages1;

                gl.pushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Test Group");
                applyFilters(filter1base);

                // First nested verification
                resMessages1 = genMessages(false, "Filtered run, pushed one debug group");
                verify(refMessages, resMessages1, filter1full);

                {
                    // Generate reference (filters iherited again)
                    const vector<MessageFilter> filter2base =
                        genFilters(refMessages, vector<MessageFilter>(), baseSeed ^ 0x43211234, 4);
                    const vector<MessageFilter> filter2full = join(filter1full, filter2base);
                    tcu::ScopedLogSection section2(log, "", "Pushing Debug Group");
                    vector<MessageData> resMessages2;

                    gl.pushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Nested Test Group");
                    applyFilters(filter2base);

                    // Second nested verification
                    resMessages2 = genMessages(false, "Filtered run, pushed two debug groups");
                    verify(refMessages, resMessages2, filter2full);

                    gl.popDebugGroup();
                }

                // First restore verification
                resMessages1 = genMessages(false, "Filtered run, popped second debug group");
                verify(refMessages, resMessages1, filter1full);

                gl.popDebugGroup();
            }

            // restore verification
            resMessages0 = genMessages(false, "Filtered run, popped first debug group");
            verify(refMessages, resMessages0, filter0);

            if (!isDebugContext() && refMessages.empty())
                m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING,
                                    "Verification accuracy is lacking without a debug context");
        }
    }
    catch (...)
    {
        gl.disable(GL_DEBUG_OUTPUT);
        gl.debugMessageCallback(DE_NULL, DE_NULL);
        throw;
    }

    gl.disable(GL_DEBUG_OUTPUT);
    gl.debugMessageCallback(DE_NULL, DE_NULL);
    m_results.setTestContextResult(m_testCtx);
    return STOP;
}

// Basic grouping functionality
class GroupCase : public BaseCase
{
public:
    GroupCase(Context &ctx, const char *name, const char *desc);
    virtual ~GroupCase()
    {
    }

    virtual IterateResult iterate(void);

private:
    virtual void callback(GLenum source, GLenum type, GLuint id, GLenum severity, const string &message);

    MessageData m_lastMessage;
};

GroupCase::GroupCase(Context &ctx, const char *name, const char *desc) : BaseCase(ctx, name, desc)
{
}

GroupCase::IterateResult GroupCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::TestLog &log        = m_testCtx.getLog();
    glu::CallLogWrapper wrapper(gl, log);

    gl.enable(GL_DEBUG_OUTPUT);
    gl.enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    gl.debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, false); // disable all
    gl.debugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, DE_NULL,
                           true); // enable API errors
    gl.debugMessageControl(GL_DEBUG_SOURCE_APPLICATION, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL,
                           true); // enable application messages
    gl.debugMessageControl(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL,
                           true); // enable third party messages
    gl.debugMessageCallback(callbackHandle, this);

    wrapper.enableLogging(true);
    wrapper.glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1234, -1, "Pushed debug stack");
    verifyMessage(m_lastMessage, GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP, 1234,
                  GL_DEBUG_SEVERITY_NOTIFICATION);
    wrapper.glPopDebugGroup();
    verifyMessage(m_lastMessage, GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP, 1234,
                  GL_DEBUG_SEVERITY_NOTIFICATION);

    wrapper.glPushDebugGroup(GL_DEBUG_SOURCE_THIRD_PARTY, 4231, -1, "Pushed debug stack");
    verifyMessage(m_lastMessage, GL_DEBUG_SOURCE_THIRD_PARTY, GL_DEBUG_TYPE_PUSH_GROUP, 4231,
                  GL_DEBUG_SEVERITY_NOTIFICATION);
    wrapper.glPopDebugGroup();
    verifyMessage(m_lastMessage, GL_DEBUG_SOURCE_THIRD_PARTY, GL_DEBUG_TYPE_POP_GROUP, 4231,
                  GL_DEBUG_SEVERITY_NOTIFICATION);

    gl.debugMessageCallback(DE_NULL, DE_NULL);
    gl.disable(GL_DEBUG_OUTPUT);

    m_results.setTestContextResult(m_testCtx);

    return STOP;
}

void GroupCase::callback(GLenum source, GLenum type, GLuint id, GLenum severity, const string &message)
{
    m_lastMessage = MessageData(MessageID(source, type, id), severity, message);
}

// Asynchronous debug output
class AsyncCase : public BaseCase
{
public:
    AsyncCase(Context &ctx, const char *name, const char *desc, const vector<TestFunctionWrapper> &errorFuncs,
              bool useCallbacks);
    virtual ~AsyncCase(void)
    {
    }

    virtual IterateResult iterate(void);

    virtual void expectMessage(glw::GLenum source, glw::GLenum type);

private:
    struct MessageCount
    {
        int received;
        int expected;

        MessageCount(void) : received(0), expected(0)
        {
        }
    };
    typedef map<MessageID, MessageCount> MessageCounter;

    enum VerifyState
    {
        VERIFY_PASS = 0,
        VERIFY_MINIMUM,
        VERIFY_FAIL,

        VERIFY_LAST
    };

    virtual void callback(glw::GLenum source, glw::GLenum type, glw::GLuint id, glw::GLenum severity,
                          const std::string &message);
    VerifyState verify(bool uselog);
    void fetchLogMessages(void);

    const vector<TestFunctionWrapper> m_errorFuncs;
    const bool m_useCallbacks;

    MessageCounter m_counts;

    de::Mutex m_mutex;
};

AsyncCase::AsyncCase(Context &ctx, const char *name, const char *desc, const vector<TestFunctionWrapper> &errorFuncs,
                     bool useCallbacks)
    : BaseCase(ctx, name, desc)
    , m_errorFuncs(errorFuncs)
    , m_useCallbacks(useCallbacks)
{
}

AsyncCase::IterateResult AsyncCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::TestLog &log        = m_testCtx.getLog();
    DebugMessageTestContext context =
        DebugMessageTestContext(*this, m_context.getRenderContext(), m_context.getContextInfo(), log, m_results, true);
    const int maxWait  = 10000; // ms
    const int warnWait = 100;

    // Clear log from earlier messages
    {
        GLint numMessages = 0;
        gl.getIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages);
        gl.getDebugMessageLog(numMessages, 0, DE_NULL, DE_NULL, DE_NULL, DE_NULL, DE_NULL, DE_NULL);
    }

    gl.enable(GL_DEBUG_OUTPUT);
    gl.enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    gl.debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, DE_NULL, false);

    // Some messages could be dependent on the value of DEBUG_OUTPUT_SYNCHRONOUS so only use API errors which should be generated in all cases
    gl.debugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, DE_NULL, true);

    if (m_useCallbacks) // will use log otherwise
        gl.debugMessageCallback(callbackHandle, this);
    else
        gl.debugMessageCallback(DE_NULL, DE_NULL);

    // Reference run (synchoronous)
    {
        tcu::ScopedLogSection section(log, "reference run", "Reference run (synchronous)");

        for (int ndx = 0; ndx < int(m_errorFuncs.size()); ndx++)
            m_errorFuncs[ndx].call(context);
    }

    if (m_counts.empty())
    {
        if (!isDebugContext())
            m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING,
                                "Need debug context to guarantee implementation behaviour (see command line options)");

        log << TestLog::Message << "Reference run produced no messages, nothing to verify" << TestLog::EndMessage;

        gl.debugMessageCallback(DE_NULL, DE_NULL);
        gl.disable(GL_DEBUG_OUTPUT);

        m_results.setTestContextResult(m_testCtx);
        return STOP;
    }

    for (MessageCounter::iterator itr = m_counts.begin(); itr != m_counts.end(); itr++)
    {
        itr->second.expected = itr->second.received;
        itr->second.received = 0;
    }

    gl.disable(GL_DEBUG_OUTPUT_SYNCHRONOUS);

    // Result run (async)
    for (int ndx = 0; ndx < int(m_errorFuncs.size()); ndx++)
        m_errorFuncs[ndx].call(context);

    // Repatedly try verification, new results may be added to m_receivedMessages at any time
    {
        tcu::ScopedLogSection section(log, "result run", "Result run (asynchronous)");
        VerifyState lastTimelyState = VERIFY_FAIL;

        for (int waited = 0;;)
        {
            const VerifyState pass = verify(false);
            const int wait         = de::max(50, waited >> 2);

            // Pass (possibly due to time limit)
            if (pass == VERIFY_PASS || (pass == VERIFY_MINIMUM && waited >= maxWait))
            {
                verify(true); // log

                // State changed late
                if (waited >= warnWait && lastTimelyState != pass)
                    m_results.addResult(QP_TEST_RESULT_QUALITY_WARNING,
                                        "Async messages were returned to application somewhat slowly");

                log << TestLog::Message << "Passed after ~" << waited << "ms of waiting" << TestLog::EndMessage;
                break;
            }
            // fail
            else if (waited >= maxWait)
            {
                verify(true); // log

                log << TestLog::Message << "Waited for ~" << waited << "ms without getting all expected messages"
                    << TestLog::EndMessage;
                m_results.addResult(QP_TEST_RESULT_FAIL,
                                    "Async messages were not returned to application within a reasonable timeframe");
                break;
            }

            if (waited < warnWait)
                lastTimelyState = pass;

            deSleep(wait);
            waited += wait;

            if (!m_useCallbacks)
                fetchLogMessages();
        }
    }

    gl.debugMessageCallback(DE_NULL, DE_NULL);

    gl.disable(GL_DEBUG_OUTPUT);
    m_results.setTestContextResult(m_testCtx);

    return STOP;
}

void AsyncCase::expectMessage(GLenum source, GLenum type)
{
    // Good time to clean up the queue as this should be called after most messages are generated
    if (!m_useCallbacks)
        fetchLogMessages();

    DE_UNREF(source);
    DE_UNREF(type);
}

void AsyncCase::callback(GLenum source, GLenum type, GLuint id, GLenum severity, const string &message)
{
    DE_ASSERT(m_useCallbacks);
    DE_UNREF(severity);
    DE_UNREF(message);

    de::ScopedLock lock(m_mutex);

    m_counts[MessageID(source, type, id)].received++;
}

// Note that we can never guarantee getting all messages back when using logs/fetching as the GL may create more than its log size limit during an arbitrary period of time
void AsyncCase::fetchLogMessages(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    GLint numMsg             = 0;

    gl.getIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMsg);

    for (int msgNdx = 0; msgNdx < numMsg; msgNdx++)
    {
        int msgLen = 0;
        MessageData msg;

        gl.getIntegerv(GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH, &msgLen);

        TCU_CHECK_MSG(msgLen >= 0, "Negative message length");
        TCU_CHECK_MSG(msgLen < 100000, "Excessively long message");

        msg.message.resize(msgLen);
        gl.getDebugMessageLog(1, msgLen, &msg.id.source, &msg.id.type, &msg.id.id, &msg.severity, &msgLen,
                              &msg.message[0]);

        {
            const de::ScopedLock lock(m_mutex); // Don't block during API call

            m_counts[MessageID(msg.id)].received++;
        }
    }
}

AsyncCase::VerifyState AsyncCase::verify(bool uselog)
{
    using std::map;

    VerifyState retval = VERIFY_PASS;
    TestLog &log       = m_testCtx.getLog();

    const de::ScopedLock lock(m_mutex);

    for (map<MessageID, MessageCount>::const_iterator itr = m_counts.begin(); itr != m_counts.end(); itr++)
    {
        const MessageID &id = itr->first;

        const int refCount = itr->second.expected;
        const int resCount = itr->second.received;
        const bool enabled = true;

        VerificationResult result = verifyMessageCount(id, GL_DONT_CARE, refCount, resCount, enabled);

        if (uselog)
            log << TestLog::Message << result.logMessage << TestLog::EndMessage;

        if (result.result == QP_TEST_RESULT_FAIL)
            retval = VERIFY_FAIL;
        else if (result.result != QP_TEST_RESULT_PASS && retval == VERIFY_PASS)
            retval = VERIFY_MINIMUM;
    }

    return retval;
}

// Tests debug labels
class LabelCase : public TestCase
{
public:
    LabelCase(Context &ctx, const char *name, const char *desc, GLenum identifier);
    virtual ~LabelCase(void)
    {
    }

    virtual IterateResult iterate(void);

private:
    GLenum m_identifier;
};

LabelCase::LabelCase(Context &ctx, const char *name, const char *desc, GLenum identifier)
    : TestCase(ctx, name, desc)
    , m_identifier(identifier)
{
}

LabelCase::IterateResult LabelCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const char *const msg    = "This is a debug label";
    GLuint object            = 0;
    int outlen               = -1;
    char buffer[64];

    switch (m_identifier)
    {
    case GL_BUFFER:
        gl.genBuffers(1, &object);
        gl.bindBuffer(GL_ARRAY_BUFFER, object);
        gl.bindBuffer(GL_ARRAY_BUFFER, 0);
        break;

    case GL_SHADER:
        object = gl.createShader(GL_FRAGMENT_SHADER);
        break;

    case GL_PROGRAM:
        object = gl.createProgram();
        break;

    case GL_QUERY:
        gl.genQueries(1, &object);
        gl.beginQuery(GL_ANY_SAMPLES_PASSED, object); // Create
        gl.endQuery(GL_ANY_SAMPLES_PASSED);           // Cleanup
        break;

    case GL_PROGRAM_PIPELINE:
        gl.genProgramPipelines(1, &object);
        gl.bindProgramPipeline(object); // Create
        gl.bindProgramPipeline(0);      // Cleanup
        break;

    case GL_TRANSFORM_FEEDBACK:
        gl.genTransformFeedbacks(1, &object);
        gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, object);
        gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
        break;

    case GL_SAMPLER:
        gl.genSamplers(1, &object);
        gl.bindSampler(0, object);
        gl.bindSampler(0, 0);
        break;

    case GL_TEXTURE:
        gl.genTextures(1, &object);
        gl.bindTexture(GL_TEXTURE_2D, object);
        gl.bindTexture(GL_TEXTURE_2D, 0);
        break;

    case GL_RENDERBUFFER:
        gl.genRenderbuffers(1, &object);
        gl.bindRenderbuffer(GL_RENDERBUFFER, object);
        gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
        break;

    case GL_FRAMEBUFFER:
        gl.genFramebuffers(1, &object);
        gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, object);
        gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, m_context.getRenderContext().getDefaultFramebuffer());
        break;

    default:
        DE_FATAL("Invalid identifier");
    }

    gl.objectLabel(m_identifier, object, -1, msg);

    deMemset(buffer, 'X', sizeof(buffer));
    gl.getObjectLabel(m_identifier, object, sizeof(buffer), &outlen, buffer);

    if (outlen == 0)
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to query debug label from object");
    else if (deStringEqual(msg, buffer))
    {
        m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\"" << TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    }
    else
    {
        buffer[63] = '\0'; // make sure buffer is null terminated before printing
        m_testCtx.getLog() << TestLog::Message << "Query returned wrong string: expected \"" << msg << "\" but got \""
                           << buffer << "\"" << TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Query returned wrong label");
    }

    switch (m_identifier)
    {
    case GL_BUFFER:
        gl.deleteBuffers(1, &object);
        break;
    case GL_SHADER:
        gl.deleteShader(object);
        break;
    case GL_PROGRAM:
        gl.deleteProgram(object);
        break;
    case GL_QUERY:
        gl.deleteQueries(1, &object);
        break;
    case GL_PROGRAM_PIPELINE:
        gl.deleteProgramPipelines(1, &object);
        break;
    case GL_TRANSFORM_FEEDBACK:
        gl.deleteTransformFeedbacks(1, &object);
        break;
    case GL_SAMPLER:
        gl.deleteSamplers(1, &object);
        break;
    case GL_TEXTURE:
        gl.deleteTextures(1, &object);
        break;
    case GL_RENDERBUFFER:
        gl.deleteRenderbuffers(1, &object);
        break;
    case GL_FRAMEBUFFER:
        gl.deleteFramebuffers(1, &object);
        break;

    default:
        DE_FATAL("Invalid identifier");
    }

    return STOP;
}

DebugMessageTestContext::DebugMessageTestContext(BaseCase &host, glu::RenderContext &renderCtx,
                                                 const glu::ContextInfo &ctxInfo, tcu::TestLog &log,
                                                 tcu::ResultCollector &results, bool enableLog)
    : NegativeTestContext(host, renderCtx, ctxInfo, log, results, enableLog)
    , m_debugHost(host)
{
}

DebugMessageTestContext::~DebugMessageTestContext(void)
{
}

void DebugMessageTestContext::expectMessage(GLenum source, GLenum type)
{
    m_debugHost.expectMessage(source, type);
}

class SyncLabelCase : public TestCase
{
public:
    SyncLabelCase(Context &ctx, const char *name, const char *desc);
    virtual IterateResult iterate(void);
};

SyncLabelCase::SyncLabelCase(Context &ctx, const char *name, const char *desc) : TestCase(ctx, name, desc)
{
}

SyncLabelCase::IterateResult SyncLabelCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const char *const msg    = "This is a debug label";
    int outlen               = -1;
    char buffer[64];

    glw::GLsync sync = gl.fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    GLU_EXPECT_NO_ERROR(gl.getError(), "fenceSync");

    gl.objectPtrLabel(sync, -1, msg);

    deMemset(buffer, 'X', sizeof(buffer));
    gl.getObjectPtrLabel(sync, sizeof(buffer), &outlen, buffer);

    if (outlen == 0)
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Failed to query debug label from object");
    else if (deStringEqual(msg, buffer))
    {
        m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\"" << TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    }
    else
    {
        buffer[63] = '\0'; // make sure buffer is null terminated before printing
        m_testCtx.getLog() << TestLog::Message << "Query returned wrong string: expected \"" << msg << "\" but got \""
                           << buffer << "\"" << TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Query returned wrong label");
    }

    gl.deleteSync(sync);

    return STOP;
}

class InitialLabelCase : public TestCase
{
public:
    InitialLabelCase(Context &ctx, const char *name, const char *desc);
    virtual IterateResult iterate(void);
};

InitialLabelCase::InitialLabelCase(Context &ctx, const char *name, const char *desc) : TestCase(ctx, name, desc)
{
}

InitialLabelCase::IterateResult InitialLabelCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int outlen = -1;
    GLuint shader;
    glw::GLsync sync;
    char buffer[64];

    sync = gl.fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "fenceSync");

    shader = gl.createShader(GL_FRAGMENT_SHADER);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "createShader");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Shader", "Shader object");
        m_testCtx.getLog() << TestLog::Message << "Querying initial value" << TestLog::EndMessage;

        buffer[0] = 'X';
        outlen    = -1;
        gl.getObjectLabel(GL_SHADER, shader, sizeof(buffer), &outlen, buffer);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectLabel");

        if (outlen != 0)
            result.fail("'length' was not zero, got " + de::toString(outlen));
        else if (buffer[0] != '\0')
            result.fail("label was not null terminated");
        else
            m_testCtx.getLog() << TestLog::Message << "Got 0-sized null-terminated string." << TestLog::EndMessage;
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Sync", "Sync object");
        m_testCtx.getLog() << TestLog::Message << "Querying initial value" << TestLog::EndMessage;

        buffer[0] = 'X';
        outlen    = -1;
        gl.getObjectPtrLabel(sync, sizeof(buffer), &outlen, buffer);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectPtrLabel");

        if (outlen != 0)
            result.fail("'length' was not zero, got " + de::toString(outlen));
        else if (buffer[0] != '\0')
            result.fail("label was not null terminated");
        else
            m_testCtx.getLog() << TestLog::Message << "Got 0-sized null-terminated string." << TestLog::EndMessage;
    }

    gl.deleteShader(shader);
    gl.deleteSync(sync);

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class ClearLabelCase : public TestCase
{
public:
    ClearLabelCase(Context &ctx, const char *name, const char *desc);
    virtual IterateResult iterate(void);
};

ClearLabelCase::ClearLabelCase(Context &ctx, const char *name, const char *desc) : TestCase(ctx, name, desc)
{
}

ClearLabelCase::IterateResult ClearLabelCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    static const struct
    {
        const char *description;
        int length;
    } s_clearMethods[] = {
        {" with NULL label and 0 length", 0},
        {" with NULL label and 1 length", 1},
        {" with NULL label and negative length", -1},
    };

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    const char *const msg = "This is a debug label";
    int outlen            = -1;
    GLuint shader;
    glw::GLsync sync;
    char buffer[64];

    sync = gl.fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "fenceSync");

    shader = gl.createShader(GL_FRAGMENT_SHADER);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "createShader");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Shader", "Shader object");

        for (int methodNdx = 0; methodNdx < DE_LENGTH_OF_ARRAY(s_clearMethods); ++methodNdx)
        {
            m_testCtx.getLog() << TestLog::Message << "Setting label to string: \"" << msg << "\""
                               << TestLog::EndMessage;
            gl.objectLabel(GL_SHADER, shader, -2, msg);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectLabel");

            m_testCtx.getLog() << TestLog::Message << "Clearing label " << s_clearMethods[methodNdx].description
                               << TestLog::EndMessage;
            gl.objectLabel(GL_SHADER, shader, s_clearMethods[methodNdx].length, DE_NULL);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectLabel");

            m_testCtx.getLog() << TestLog::Message << "Querying label" << TestLog::EndMessage;
            buffer[0] = 'X';
            outlen    = -1;
            gl.getObjectLabel(GL_SHADER, shader, sizeof(buffer), &outlen, buffer);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectLabel");

            if (outlen != 0)
                result.fail("'length' was not zero, got " + de::toString(outlen));
            else if (buffer[0] != '\0')
                result.fail("label was not null terminated");
            else
                m_testCtx.getLog() << TestLog::Message << "Got 0-sized null-terminated string." << TestLog::EndMessage;
        }
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Sync", "Sync object");

        for (int methodNdx = 0; methodNdx < DE_LENGTH_OF_ARRAY(s_clearMethods); ++methodNdx)
        {
            m_testCtx.getLog() << TestLog::Message << "Setting label to string: \"" << msg << "\""
                               << TestLog::EndMessage;
            gl.objectPtrLabel(sync, -2, msg);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectPtrLabel");

            m_testCtx.getLog() << TestLog::Message << "Clearing label " << s_clearMethods[methodNdx].description
                               << TestLog::EndMessage;
            gl.objectPtrLabel(sync, s_clearMethods[methodNdx].length, DE_NULL);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectPtrLabel");

            m_testCtx.getLog() << TestLog::Message << "Querying label" << TestLog::EndMessage;
            buffer[0] = 'X';
            outlen    = -1;
            gl.getObjectPtrLabel(sync, sizeof(buffer), &outlen, buffer);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectPtrLabel");

            if (outlen != 0)
                result.fail("'length' was not zero, got " + de::toString(outlen));
            else if (buffer[0] != '\0')
                result.fail("label was not null terminated");
            else
                m_testCtx.getLog() << TestLog::Message << "Got 0-sized null-terminated string." << TestLog::EndMessage;
        }
    }

    gl.deleteShader(shader);
    gl.deleteSync(sync);

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class SpecifyWithLengthCase : public TestCase
{
public:
    SpecifyWithLengthCase(Context &ctx, const char *name, const char *desc);
    virtual IterateResult iterate(void);
};

SpecifyWithLengthCase::SpecifyWithLengthCase(Context &ctx, const char *name, const char *desc)
    : TestCase(ctx, name, desc)
{
}

SpecifyWithLengthCase::IterateResult SpecifyWithLengthCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    const char *const msg     = "This is a debug label";
    const char *const clipMsg = "This is a de";
    int outlen                = -1;
    GLuint shader;
    glw::GLsync sync;
    char buffer[64];

    sync = gl.fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "fenceSync");

    shader = gl.createShader(GL_FRAGMENT_SHADER);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "createShader");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Shader", "Shader object");

        m_testCtx.getLog() << TestLog::Message << "Setting label to string: \"" << msg << "\" with length 12"
                           << TestLog::EndMessage;
        gl.objectLabel(GL_SHADER, shader, 12, msg);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectLabel");

        m_testCtx.getLog() << TestLog::Message << "Querying label" << TestLog::EndMessage;
        deMemset(buffer, 'X', sizeof(buffer));
        gl.getObjectLabel(GL_SHADER, shader, sizeof(buffer), &outlen, buffer);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectLabel");

        if (outlen != 12)
            result.fail("'length' was not 12, got " + de::toString(outlen));
        else if (deStringEqual(clipMsg, buffer))
        {
            m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                               << TestLog::EndMessage;
        }
        else
        {
            buffer[63] = '\0'; // make sure buffer is null terminated before printing
            m_testCtx.getLog() << TestLog::Message << "Query returned wrong string: expected \"" << clipMsg
                               << "\" but got \"" << buffer << "\"" << TestLog::EndMessage;
            result.fail("Query returned wrong label");
        }
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Sync", "Sync object");

        m_testCtx.getLog() << TestLog::Message << "Setting label to string: \"" << msg << "\" with length 12"
                           << TestLog::EndMessage;
        gl.objectPtrLabel(sync, 12, msg);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectPtrLabel");

        m_testCtx.getLog() << TestLog::Message << "Querying label" << TestLog::EndMessage;
        deMemset(buffer, 'X', sizeof(buffer));
        gl.getObjectPtrLabel(sync, sizeof(buffer), &outlen, buffer);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectPtrLabel");

        if (outlen != 12)
            result.fail("'length' was not 12, got " + de::toString(outlen));
        else if (deStringEqual(clipMsg, buffer))
        {
            m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                               << TestLog::EndMessage;
        }
        else
        {
            buffer[63] = '\0'; // make sure buffer is null terminated before printing
            m_testCtx.getLog() << TestLog::Message << "Query returned wrong string: expected \"" << clipMsg
                               << "\" but got \"" << buffer << "\"" << TestLog::EndMessage;
            result.fail("Query returned wrong label");
        }
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "ZeroSized", "ZeroSized");

        m_testCtx.getLog() << TestLog::Message << "Setting label to string: \"" << msg << "\" with length 0"
                           << TestLog::EndMessage;
        gl.objectLabel(GL_SHADER, shader, 0, msg);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectLabel");

        m_testCtx.getLog() << TestLog::Message << "Querying label" << TestLog::EndMessage;
        deMemset(buffer, 'X', sizeof(buffer));
        gl.getObjectLabel(GL_SHADER, shader, sizeof(buffer), &outlen, buffer);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectLabel");

        if (outlen != 0)
            result.fail("'length' was not zero, got " + de::toString(outlen));
        else if (buffer[0] != '\0')
            result.fail("label was not null terminated");
        else
            m_testCtx.getLog() << TestLog::Message << "Got 0-sized null-terminated string." << TestLog::EndMessage;
    }

    gl.deleteShader(shader);
    gl.deleteSync(sync);

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class BufferLimitedLabelCase : public TestCase
{
public:
    BufferLimitedLabelCase(Context &ctx, const char *name, const char *desc);
    virtual IterateResult iterate(void);
};

BufferLimitedLabelCase::BufferLimitedLabelCase(Context &ctx, const char *name, const char *desc)
    : TestCase(ctx, name, desc)
{
}

BufferLimitedLabelCase::IterateResult BufferLimitedLabelCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    const char *const msg = "This is a debug label";
    int outlen            = -1;
    GLuint shader;
    glw::GLsync sync;
    char buffer[64];

    sync = gl.fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "fenceSync");

    shader = gl.createShader(GL_FRAGMENT_SHADER);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "createShader");

    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "Shader", "Shader object");

        m_testCtx.getLog() << TestLog::Message << "Setting label to string: \"" << msg << "\"" << TestLog::EndMessage;
        gl.objectLabel(GL_SHADER, shader, -1, msg);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectLabel");

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "QueryAll", "Query All");

            m_testCtx.getLog() << TestLog::Message << "Querying whole label, buffer size = 22" << TestLog::EndMessage;
            deMemset(buffer, 'X', sizeof(buffer));
            gl.getObjectLabel(GL_SHADER, shader, 22, &outlen, buffer);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectLabel");

            if (outlen != 21)
                result.fail("'length' was not 21, got " + de::toString(outlen));
            else if (buffer[outlen] != '\0')
                result.fail("Buffer was not null-terminated");
            else if (buffer[outlen + 1] != 'X')
                result.fail("Query wrote over buffer bound");
            else if (!deStringEqual(msg, buffer))
            {
                buffer[63] = '\0'; // make sure buffer is null terminated before printing
                m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                                   << TestLog::EndMessage;
                result.fail("Query returned wrong label");
            }
            else
                m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                                   << TestLog::EndMessage;
        }
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "QueryAllNoSize", "Query all without size");

            m_testCtx.getLog() << TestLog::Message << "Querying whole label, buffer size = 22" << TestLog::EndMessage;
            deMemset(buffer, 'X', sizeof(buffer));
            gl.getObjectLabel(GL_SHADER, shader, 22, DE_NULL, buffer);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectLabel");

            buffer[63] = '\0'; // make sure buffer is null terminated before strlen

            if (strlen(buffer) != 21)
                result.fail("Buffer length was not 21");
            else if (buffer[21] != '\0')
                result.fail("Buffer was not null-terminated");
            else if (buffer[22] != 'X')
                result.fail("Query wrote over buffer bound");
            else if (!deStringEqual(msg, buffer))
            {
                m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                                   << TestLog::EndMessage;
                result.fail("Query returned wrong label");
            }
            else
                m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                                   << TestLog::EndMessage;
        }
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "QueryLess", "Query substring");

            m_testCtx.getLog() << TestLog::Message << "Querying whole label, buffer size = 2" << TestLog::EndMessage;
            deMemset(buffer, 'X', sizeof(buffer));
            gl.getObjectLabel(GL_SHADER, shader, 2, &outlen, buffer);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectLabel");

            if (outlen != 1)
                result.fail("'length' was not 1, got " + de::toString(outlen));
            else if (buffer[outlen] != '\0')
                result.fail("Buffer was not null-terminated");
            else if (buffer[outlen + 1] != 'X')
                result.fail("Query wrote over buffer bound");
            else if (!deStringBeginsWith(msg, buffer))
            {
                buffer[63] = '\0'; // make sure buffer is null terminated before printing
                m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                                   << TestLog::EndMessage;
                result.fail("Query returned wrong label");
            }
            else
                m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                                   << TestLog::EndMessage;
        }
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "QueryNone", "Query one character");

            m_testCtx.getLog() << TestLog::Message << "Querying whole label, buffer size = 1" << TestLog::EndMessage;
            deMemset(buffer, 'X', sizeof(buffer));
            gl.getObjectLabel(GL_SHADER, shader, 1, &outlen, buffer);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectLabel");

            if (outlen != 0)
                result.fail("'length' was not 0, got " + de::toString(outlen));
            else if (buffer[outlen] != '\0')
                result.fail("Buffer was not null-terminated");
            else if (buffer[outlen + 1] != 'X')
                result.fail("Query wrote over buffer bound");
            else
                m_testCtx.getLog() << TestLog::Message << "Query returned zero-sized null-terminated string"
                                   << TestLog::EndMessage;
        }
    }

    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "Sync", "Sync object");

        m_testCtx.getLog() << TestLog::Message << "Setting label to string: \"" << msg << "\"" << TestLog::EndMessage;
        gl.objectPtrLabel(sync, -1, msg);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectPtrLabel");

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "QueryAll", "Query All");

            m_testCtx.getLog() << TestLog::Message << "Querying whole label, buffer size = 22" << TestLog::EndMessage;
            deMemset(buffer, 'X', sizeof(buffer));
            gl.getObjectPtrLabel(sync, 22, &outlen, buffer);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectPtrLabel");

            if (outlen != 21)
                result.fail("'length' was not 21, got " + de::toString(outlen));
            else if (buffer[outlen] != '\0')
                result.fail("Buffer was not null-terminated");
            else if (buffer[outlen + 1] != 'X')
                result.fail("Query wrote over buffer bound");
            else if (!deStringEqual(msg, buffer))
            {
                buffer[63] = '\0'; // make sure buffer is null terminated before printing
                m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                                   << TestLog::EndMessage;
                result.fail("Query returned wrong label");
            }
            else
                m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                                   << TestLog::EndMessage;
        }
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "QueryAllNoSize", "Query all without size");

            m_testCtx.getLog() << TestLog::Message << "Querying whole label, buffer size = 22" << TestLog::EndMessage;
            deMemset(buffer, 'X', sizeof(buffer));
            gl.getObjectPtrLabel(sync, 22, DE_NULL, buffer);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectPtrLabel");

            buffer[63] = '\0'; // make sure buffer is null terminated before strlen

            if (strlen(buffer) != 21)
                result.fail("Buffer length was not 21");
            else if (buffer[21] != '\0')
                result.fail("Buffer was not null-terminated");
            else if (buffer[22] != 'X')
                result.fail("Query wrote over buffer bound");
            else if (!deStringEqual(msg, buffer))
            {
                m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                                   << TestLog::EndMessage;
                result.fail("Query returned wrong label");
            }
            else
                m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                                   << TestLog::EndMessage;
        }
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "QueryLess", "Query substring");

            m_testCtx.getLog() << TestLog::Message << "Querying whole label, buffer size = 2" << TestLog::EndMessage;
            deMemset(buffer, 'X', sizeof(buffer));
            gl.getObjectPtrLabel(sync, 2, &outlen, buffer);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectPtrLabel");

            if (outlen != 1)
                result.fail("'length' was not 1, got " + de::toString(outlen));
            else if (buffer[outlen] != '\0')
                result.fail("Buffer was not null-terminated");
            else if (buffer[outlen + 1] != 'X')
                result.fail("Query wrote over buffer bound");
            else if (!deStringBeginsWith(msg, buffer))
            {
                buffer[63] = '\0'; // make sure buffer is null terminated before printing
                m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                                   << TestLog::EndMessage;
                result.fail("Query returned wrong label");
            }
            else
                m_testCtx.getLog() << TestLog::Message << "Query returned string: \"" << buffer << "\""
                                   << TestLog::EndMessage;
        }
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "QueryNone", "Query one character");

            m_testCtx.getLog() << TestLog::Message << "Querying whole label, buffer size = 1" << TestLog::EndMessage;
            deMemset(buffer, 'X', sizeof(buffer));
            gl.getObjectPtrLabel(sync, 1, &outlen, buffer);
            GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectPtrLabel");

            if (outlen != 0)
                result.fail("'length' was not 0, got " + de::toString(outlen));
            else if (buffer[outlen] != '\0')
                result.fail("Buffer was not null-terminated");
            else if (buffer[outlen + 1] != 'X')
                result.fail("Query wrote over buffer bound");
            else
                m_testCtx.getLog() << TestLog::Message << "Query returned zero-sized null-terminated string"
                                   << TestLog::EndMessage;
        }
    }

    gl.deleteShader(shader);
    gl.deleteSync(sync);

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class LabelMaxSizeCase : public TestCase
{
public:
    LabelMaxSizeCase(Context &ctx, const char *name, const char *desc);
    virtual IterateResult iterate(void);
};

LabelMaxSizeCase::LabelMaxSizeCase(Context &ctx, const char *name, const char *desc) : TestCase(ctx, name, desc)
{
}

LabelMaxSizeCase::IterateResult LabelMaxSizeCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int maxLabelLen = -1;
    int outlen      = -1;
    GLuint shader;
    glw::GLsync sync;

    gl.getIntegerv(GL_MAX_LABEL_LENGTH, &maxLabelLen);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "GL_MAX_LABEL_LENGTH");

    m_testCtx.getLog() << TestLog::Message << "GL_MAX_LABEL_LENGTH = " << maxLabelLen << TestLog::EndMessage;

    if (maxLabelLen < 256)
        throw tcu::TestError("maxLabelLen was less than required (256)");
    if (maxLabelLen > 8192)
    {
        m_testCtx.getLog()
            << TestLog::Message
            << "GL_MAX_LABEL_LENGTH is very large. Application having larger labels is unlikely, skipping test."
            << TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        return STOP;
    }

    sync = gl.fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "fenceSync");

    shader = gl.createShader(GL_FRAGMENT_SHADER);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "createShader");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Shader", "Shader object");
        std::vector<char> buffer(maxLabelLen, 'X');
        std::vector<char> readBuffer(maxLabelLen, 'X');

        buffer[maxLabelLen - 1] = '\0';

        m_testCtx.getLog() << TestLog::Message << "Setting max length label, with implicit size. (length = -1)"
                           << TestLog::EndMessage;
        gl.objectLabel(GL_SHADER, shader, -1, &buffer[0]);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectLabel");

        m_testCtx.getLog() << TestLog::Message << "Querying label back" << TestLog::EndMessage;
        outlen = -1;
        gl.getObjectLabel(GL_SHADER, shader, maxLabelLen, &outlen, &readBuffer[0]);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectLabel");

        if (outlen != maxLabelLen - 1)
            result.fail("'length' was not " + de::toString(maxLabelLen - 1) + ", got " + de::toString(outlen));
        else if (readBuffer[outlen] != '\0')
            result.fail("Buffer was not null-terminated");

        m_testCtx.getLog() << TestLog::Message
                           << "Setting max length label, with explicit size. (length = " << (maxLabelLen - 1) << ")"
                           << TestLog::EndMessage;
        gl.objectLabel(GL_SHADER, shader, maxLabelLen - 1, &buffer[0]);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectLabel");

        m_testCtx.getLog() << TestLog::Message << "Querying label back" << TestLog::EndMessage;
        outlen                      = -1;
        readBuffer[maxLabelLen - 1] = 'X';
        gl.getObjectLabel(GL_SHADER, shader, maxLabelLen, &outlen, &readBuffer[0]);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectLabel");

        if (outlen != maxLabelLen - 1)
            result.fail("'length' was not " + de::toString(maxLabelLen - 1) + ", got " + de::toString(outlen));
        else if (readBuffer[outlen] != '\0')
            result.fail("Buffer was not null-terminated");
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Sync", "Sync object");
        std::vector<char> buffer(maxLabelLen, 'X');
        std::vector<char> readBuffer(maxLabelLen, 'X');

        buffer[maxLabelLen - 1] = '\0';

        m_testCtx.getLog() << TestLog::Message << "Setting max length label, with implicit size. (length = -1)"
                           << TestLog::EndMessage;
        gl.objectPtrLabel(sync, -1, &buffer[0]);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectPtrLabel");

        m_testCtx.getLog() << TestLog::Message << "Querying label back" << TestLog::EndMessage;
        outlen = -1;
        gl.getObjectPtrLabel(sync, maxLabelLen, &outlen, &readBuffer[0]);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectPtrLabel");

        if (outlen != maxLabelLen - 1)
            result.fail("'length' was not " + de::toString(maxLabelLen - 1) + ", got " + de::toString(outlen));
        else if (readBuffer[outlen] != '\0')
            result.fail("Buffer was not null-terminated");

        m_testCtx.getLog() << TestLog::Message
                           << "Setting max length label, with explicit size. (length = " << (maxLabelLen - 1) << ")"
                           << TestLog::EndMessage;
        gl.objectPtrLabel(sync, maxLabelLen - 1, &buffer[0]);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectPtrLabel");

        m_testCtx.getLog() << TestLog::Message << "Querying label back" << TestLog::EndMessage;
        outlen                      = -1;
        readBuffer[maxLabelLen - 1] = 'X';
        gl.getObjectPtrLabel(sync, maxLabelLen, &outlen, &readBuffer[0]);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectPtrLabel");

        if (outlen != maxLabelLen - 1)
            result.fail("'length' was not " + de::toString(maxLabelLen - 1) + ", got " + de::toString(outlen));
        else if (readBuffer[outlen] != '\0')
            result.fail("Buffer was not null-terminated");
    }

    gl.deleteShader(shader);
    gl.deleteSync(sync);

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class LabelLengthCase : public TestCase
{
public:
    LabelLengthCase(Context &ctx, const char *name, const char *desc);
    virtual IterateResult iterate(void);
};

LabelLengthCase::LabelLengthCase(Context &ctx, const char *name, const char *desc) : TestCase(ctx, name, desc)
{
}

LabelLengthCase::IterateResult LabelLengthCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    const char *const msg = "This is a debug label";
    int outlen            = -1;
    GLuint shader;
    glw::GLsync sync;

    sync = gl.fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "fenceSync");

    shader = gl.createShader(GL_FRAGMENT_SHADER);
    GLS_COLLECT_GL_ERROR(result, gl.getError(), "createShader");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Shader", "Shader object");

        m_testCtx.getLog() << TestLog::Message << "Querying label length" << TestLog::EndMessage;
        outlen = -1;
        gl.getObjectLabel(GL_SHADER, shader, 0, &outlen, DE_NULL);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectLabel");

        if (outlen != 0)
            result.fail("'length' was not 0, got " + de::toString(outlen));
        else
            m_testCtx.getLog() << TestLog::Message << "Query returned length: " << outlen << TestLog::EndMessage;

        m_testCtx.getLog() << TestLog::Message << "Setting label to string: \"" << msg << "\"" << TestLog::EndMessage;
        gl.objectLabel(GL_SHADER, shader, -1, msg);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectLabel");

        m_testCtx.getLog() << TestLog::Message << "Querying label length" << TestLog::EndMessage;
        outlen = -1;
        gl.getObjectLabel(GL_SHADER, shader, 0, &outlen, DE_NULL);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectLabel");

        if (outlen != 21)
            result.fail("'length' was not 21, got " + de::toString(outlen));
        else
            m_testCtx.getLog() << TestLog::Message << "Query returned length: " << outlen << TestLog::EndMessage;
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Sync", "Sync object");

        m_testCtx.getLog() << TestLog::Message << "Querying label length" << TestLog::EndMessage;
        outlen = -1;
        gl.getObjectPtrLabel(sync, 0, &outlen, DE_NULL);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectPtrLabel");

        if (outlen != 0)
            result.fail("'length' was not 0, got " + de::toString(outlen));
        else
            m_testCtx.getLog() << TestLog::Message << "Query returned length: " << outlen << TestLog::EndMessage;

        m_testCtx.getLog() << TestLog::Message << "Setting label to string: \"" << msg << "\"" << TestLog::EndMessage;
        gl.objectPtrLabel(sync, -1, msg);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "objectPtrLabel");

        m_testCtx.getLog() << TestLog::Message << "Querying label length" << TestLog::EndMessage;
        outlen = -1;
        gl.getObjectPtrLabel(sync, 0, &outlen, DE_NULL);
        GLS_COLLECT_GL_ERROR(result, gl.getError(), "getObjectPtrLabel");

        if (outlen != 21)
            result.fail("'length' was not 21, got " + de::toString(outlen));
        else
            m_testCtx.getLog() << TestLog::Message << "Query returned length: " << outlen << TestLog::EndMessage;
    }

    gl.deleteShader(shader);
    gl.deleteSync(sync);

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class LimitQueryCase : public TestCase
{
public:
    LimitQueryCase(Context &context, const char *name, const char *description, glw::GLenum target, int limit,
                   gls::StateQueryUtil::QueryType type);

    IterateResult iterate(void);

private:
    const gls::StateQueryUtil::QueryType m_type;
    const int m_limit;
    const glw::GLenum m_target;
};

LimitQueryCase::LimitQueryCase(Context &context, const char *name, const char *description, glw::GLenum target,
                               int limit, gls::StateQueryUtil::QueryType type)
    : TestCase(context, name, description)
    , m_type(type)
    , m_limit(limit)
    , m_target(target)
{
}

LimitQueryCase::IterateResult LimitQueryCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    gl.enableLogging(true);
    gls::StateQueryUtil::verifyStateIntegerMin(result, gl, m_target, m_limit, m_type);

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class IsEnabledCase : public TestCase
{
public:
    enum InitialValue
    {
        INITIAL_CTX_IS_DEBUG = 0,
        INITIAL_FALSE,
    };

    IsEnabledCase(Context &context, const char *name, const char *description, glw::GLenum target, InitialValue initial,
                  gls::StateQueryUtil::QueryType type);

    IterateResult iterate(void);

private:
    const gls::StateQueryUtil::QueryType m_type;
    const glw::GLenum m_target;
    const InitialValue m_initial;
};

IsEnabledCase::IsEnabledCase(Context &context, const char *name, const char *description, glw::GLenum target,
                             InitialValue initial, gls::StateQueryUtil::QueryType type)
    : TestCase(context, name, description)
    , m_type(type)
    , m_target(target)
    , m_initial(initial)
{
}

IsEnabledCase::IterateResult IsEnabledCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    bool initial;

    gl.enableLogging(true);

    if (m_initial == INITIAL_FALSE)
        initial = false;
    else
    {
        DE_ASSERT(m_initial == INITIAL_CTX_IS_DEBUG);
        initial = (m_context.getRenderContext().getType().getFlags() & glu::CONTEXT_DEBUG) != 0;
    }

    // check inital value
    gls::StateQueryUtil::verifyStateBoolean(result, gl, m_target, initial, m_type);

    // check toggle

    gl.glEnable(m_target);
    GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glEnable");

    gls::StateQueryUtil::verifyStateBoolean(result, gl, m_target, true, m_type);

    gl.glDisable(m_target);
    GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glDisable");

    gls::StateQueryUtil::verifyStateBoolean(result, gl, m_target, false, m_type);

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class PositiveIntegerCase : public TestCase
{
public:
    PositiveIntegerCase(Context &context, const char *name, const char *description, glw::GLenum target,
                        gls::StateQueryUtil::QueryType type);

    IterateResult iterate(void);

private:
    const gls::StateQueryUtil::QueryType m_type;
    const glw::GLenum m_target;
};

PositiveIntegerCase::PositiveIntegerCase(Context &context, const char *name, const char *description,
                                         glw::GLenum target, gls::StateQueryUtil::QueryType type)
    : TestCase(context, name, description)
    , m_type(type)
    , m_target(target)
{
}

PositiveIntegerCase::IterateResult PositiveIntegerCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    gl.enableLogging(true);
    gls::StateQueryUtil::verifyStateIntegerMin(result, gl, m_target, 0, m_type);

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class GroupStackDepthQueryCase : public TestCase
{
public:
    GroupStackDepthQueryCase(Context &context, const char *name, const char *description,
                             gls::StateQueryUtil::QueryType type);

    IterateResult iterate(void);

private:
    const gls::StateQueryUtil::QueryType m_type;
};

GroupStackDepthQueryCase::GroupStackDepthQueryCase(Context &context, const char *name, const char *description,
                                                   gls::StateQueryUtil::QueryType type)
    : TestCase(context, name, description)
    , m_type(type)
{
}

GroupStackDepthQueryCase::IterateResult GroupStackDepthQueryCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    gl.enableLogging(true);

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Initial", "Initial");

        gls::StateQueryUtil::verifyStateInteger(result, gl, GL_DEBUG_GROUP_STACK_DEPTH, 1, m_type);
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Scoped", "Scoped");

        gl.glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Application group 1");
        gls::StateQueryUtil::verifyStateInteger(result, gl, GL_DEBUG_GROUP_STACK_DEPTH, 2, m_type);
        gl.glPopDebugGroup();
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

extern "C" void GLW_APIENTRY emptyCallback(GLenum, GLenum, GLuint, GLenum, GLsizei, const char *, const void *)
{
    // empty
}

class DebugCallbackFunctionCase : public TestCase
{
public:
    DebugCallbackFunctionCase(Context &context, const char *name, const char *description);
    IterateResult iterate(void);
};

DebugCallbackFunctionCase::DebugCallbackFunctionCase(Context &context, const char *name, const char *description)
    : TestCase(context, name, description)
{
}

DebugCallbackFunctionCase::IterateResult DebugCallbackFunctionCase::iterate(void)
{
    using namespace gls::StateQueryUtil;
    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    gl.enableLogging(true);

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Initial", "Initial");

        verifyStatePointer(result, gl, GL_DEBUG_CALLBACK_FUNCTION, 0, QUERY_POINTER);
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Set", "Set");

        gl.glDebugMessageCallback(emptyCallback, DE_NULL);
        verifyStatePointer(result, gl, GL_DEBUG_CALLBACK_FUNCTION, (const void *)emptyCallback, QUERY_POINTER);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class DebugCallbackUserParamCase : public TestCase
{
public:
    DebugCallbackUserParamCase(Context &context, const char *name, const char *description);
    IterateResult iterate(void);
};

DebugCallbackUserParamCase::DebugCallbackUserParamCase(Context &context, const char *name, const char *description)
    : TestCase(context, name, description)
{
}

DebugCallbackUserParamCase::IterateResult DebugCallbackUserParamCase::iterate(void)
{
    using namespace gls::StateQueryUtil;

    TCU_CHECK_AND_THROW(NotSupportedError, isKHRDebugSupported(m_context), "GL_KHR_debug is not supported");

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    gl.enableLogging(true);

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Initial", "Initial");

        verifyStatePointer(result, gl, GL_DEBUG_CALLBACK_USER_PARAM, 0, QUERY_POINTER);
    }

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Set", "Set");
        const void *param = (void *)(int *)0x123;

        gl.glDebugMessageCallback(emptyCallback, param);
        verifyStatePointer(result, gl, GL_DEBUG_CALLBACK_USER_PARAM, param, QUERY_POINTER);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

} // namespace

DebugTests::DebugTests(Context &context) : TestCaseGroup(context, "debug", "Debug tests")
{
}

enum CaseType
{
    CASETYPE_CALLBACK = 0,
    CASETYPE_LOG,
    CASETYPE_GETERROR,

    CASETYPE_LAST
};

tcu::TestNode *createCase(CaseType type, Context &ctx, const char *name, const char *desc, TestFunctionWrapper function)
{
    switch (type)
    {
    case CASETYPE_CALLBACK:
        return new CallbackErrorCase(ctx, name, desc, function);
    case CASETYPE_LOG:
        return new LogErrorCase(ctx, name, desc, function);
    case CASETYPE_GETERROR:
        return new GetErrorCase(ctx, name, desc, function);

    default:
        DE_FATAL("Invalid type");
    }

    return DE_NULL;
}

tcu::TestCaseGroup *createChildCases(CaseType type, Context &ctx, const char *name, const char *desc,
                                     const vector<FunctionContainer> &funcs)
{
    tcu::TestCaseGroup *host = new tcu::TestCaseGroup(ctx.getTestContext(), name, desc);

    for (size_t ndx = 0; ndx < funcs.size(); ndx++)
        host->addChild(createCase(type, ctx, funcs[ndx].name, funcs[ndx].desc, funcs[ndx].function));

    return host;
}

vector<FunctionContainer> wrapCoreFunctions(const vector<NegativeTestShared::FunctionContainer> &fns)
{
    vector<FunctionContainer> retVal;

    retVal.resize(fns.size());
    for (int ndx = 0; ndx < (int)fns.size(); ++ndx)
    {
        retVal[ndx].function = TestFunctionWrapper(fns[ndx].function);
        retVal[ndx].name     = fns[ndx].name;
        retVal[ndx].desc     = fns[ndx].desc;
    }

    return retVal;
}

void DebugTests::init(void)
{
    const vector<FunctionContainer> bufferFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeBufferApiTestFunctions());
    const vector<FunctionContainer> textureFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeTextureApiTestFunctions());
    const vector<FunctionContainer> shaderFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeShaderApiTestFunctions());
    const vector<FunctionContainer> fragmentFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeFragmentApiTestFunctions());
    const vector<FunctionContainer> vaFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeVertexArrayApiTestFunctions());
    const vector<FunctionContainer> stateFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeStateApiTestFunctions());
    const vector<FunctionContainer> tessellationFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeTessellationTestFunctions());
    const vector<FunctionContainer> atomicCounterFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeAtomicCounterTestFunctions());
    const vector<FunctionContainer> imageLoadFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeShaderImageLoadTestFunctions());
    const vector<FunctionContainer> imageStoreFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeShaderImageStoreTestFunctions());
    const vector<FunctionContainer> imageAtomicFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeShaderImageAtomicTestFunctions());
    const vector<FunctionContainer> imageAtomicExchangeFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeShaderImageAtomicExchangeTestFunctions());
    const vector<FunctionContainer> shaderFunctionFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeShaderFunctionTestFunctions());
    const vector<FunctionContainer> shaderDirectiveFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeShaderDirectiveTestFunctions());
    const vector<FunctionContainer> ssboBlockFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeSSBOBlockTestFunctions());
    const vector<FunctionContainer> preciseFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativePreciseTestFunctions());
    const vector<FunctionContainer> advancedBlendFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeAdvancedBlendEquationTestFunctions());
    const vector<FunctionContainer> shaderStorageFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeShaderStorageTestFunctions());
    const vector<FunctionContainer> sampleVariablesFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeSampleVariablesTestFunctions());
    const vector<FunctionContainer> computeFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeComputeTestFunctions());
    const vector<FunctionContainer> framebufferFetchFuncs =
        wrapCoreFunctions(NegativeTestShared::getNegativeShaderFramebufferFetchTestFunctions());
    const vector<FunctionContainer> externalFuncs = getUserMessageFuncs();

    {
        using namespace gls::StateQueryUtil;

        tcu::TestCaseGroup *const queries = new tcu::TestCaseGroup(m_testCtx, "state_query", "State query");

        static const struct
        {
            const char *name;
            const char *targetName;
            glw::GLenum target;
            int limit;
        } limits[] = {
            {"max_debug_message_length", "MAX_DEBUG_MESSAGE_LENGTH", GL_MAX_DEBUG_MESSAGE_LENGTH, 1},
            {"max_debug_logged_messages", "MAX_DEBUG_LOGGED_MESSAGES", GL_MAX_DEBUG_LOGGED_MESSAGES, 1},
            {"max_debug_group_stack_depth", "MAX_DEBUG_GROUP_STACK_DEPTH", GL_MAX_DEBUG_GROUP_STACK_DEPTH, 64},
            {"max_label_length", "MAX_LABEL_LENGTH", GL_MAX_LABEL_LENGTH, 256},
        };

        addChild(queries);

#define FOR_ALL_TYPES(X)                                 \
    do                                                   \
    {                                                    \
        {                                                \
            const char *const postfix = "_getboolean";   \
            const QueryType queryType = QUERY_BOOLEAN;   \
            X;                                           \
        }                                                \
        {                                                \
            const char *const postfix = "_getinteger";   \
            const QueryType queryType = QUERY_INTEGER;   \
            X;                                           \
        }                                                \
        {                                                \
            const char *const postfix = "_getinteger64"; \
            const QueryType queryType = QUERY_INTEGER64; \
            X;                                           \
        }                                                \
        {                                                \
            const char *const postfix = "_getfloat";     \
            const QueryType queryType = QUERY_FLOAT;     \
            X;                                           \
        }                                                \
    } while (false)
#define FOR_ALL_ENABLE_TYPES(X)                          \
    do                                                   \
    {                                                    \
        {                                                \
            const char *const postfix = "_isenabled";    \
            const QueryType queryType = QUERY_ISENABLED; \
            X;                                           \
        }                                                \
        FOR_ALL_TYPES(X);                                \
    } while (false)

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(limits); ++ndx)
        {
            FOR_ALL_TYPES(
                queries->addChild(new LimitQueryCase(m_context, (std::string(limits[ndx].name) + postfix).c_str(),
                                                     (std::string("Test ") + limits[ndx].targetName).c_str(),
                                                     limits[ndx].target, limits[ndx].limit, queryType)));
        }

        FOR_ALL_ENABLE_TYPES(queries->addChild(
            new IsEnabledCase(m_context, (std::string("debug_output") + postfix).c_str(), "Test DEBUG_OUTPUT",
                              GL_DEBUG_OUTPUT, IsEnabledCase::INITIAL_CTX_IS_DEBUG, queryType)));
        FOR_ALL_ENABLE_TYPES(queries->addChild(new IsEnabledCase(
            m_context, (std::string("debug_output_synchronous") + postfix).c_str(), "Test DEBUG_OUTPUT_SYNCHRONOUS",
            GL_DEBUG_OUTPUT_SYNCHRONOUS, IsEnabledCase::INITIAL_FALSE, queryType)));

        FOR_ALL_TYPES(queries->addChild(
            new PositiveIntegerCase(m_context, (std::string("debug_logged_messages") + postfix).c_str(),
                                    "Test DEBUG_LOGGED_MESSAGES", GL_DEBUG_LOGGED_MESSAGES, queryType)));
        FOR_ALL_TYPES(queries->addChild(new PositiveIntegerCase(
            m_context, (std::string("debug_next_logged_message_length") + postfix).c_str(),
            "Test DEBUG_NEXT_LOGGED_MESSAGE_LENGTH", GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH, queryType)));
        FOR_ALL_TYPES(queries->addChild(
            new GroupStackDepthQueryCase(m_context, (std::string("debug_group_stack_depth") + postfix).c_str(),
                                         "Test DEBUG_GROUP_STACK_DEPTH", queryType)));

        queries->addChild(new DebugCallbackFunctionCase(m_context, "debug_callback_function_getpointer",
                                                        "Test DEBUG_CALLBACK_FUNCTION"));
        queries->addChild(new DebugCallbackUserParamCase(m_context, "debug_callback_user_param_getpointer",
                                                         "Test DEBUG_CALLBACK_USER_PARAM"));

#undef FOR_ALL_TYPES
#undef FOR_ALL_ENABLE_TYPES
    }

    {
        tcu::TestCaseGroup *const negative =
            new tcu::TestCaseGroup(m_testCtx, "negative_coverage", "API error coverage with various reporting methods");

        addChild(negative);
        {
            tcu::TestCaseGroup *const host =
                new tcu::TestCaseGroup(m_testCtx, "callbacks", "Reporting of standard API errors via callback");

            negative->addChild(host);
            host->addChild(
                createChildCases(CASETYPE_CALLBACK, m_context, "buffer", "Negative Buffer API Cases", bufferFuncs));
            host->addChild(
                createChildCases(CASETYPE_CALLBACK, m_context, "texture", "Negative Texture API Cases", textureFuncs));
            host->addChild(
                createChildCases(CASETYPE_CALLBACK, m_context, "shader", "Negative Shader API Cases", shaderFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "fragment", "Negative Fragment API Cases",
                                            fragmentFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "vertex_array",
                                            "Negative Vertex Array API Cases", vaFuncs));
            host->addChild(
                createChildCases(CASETYPE_CALLBACK, m_context, "state", "Negative GL State API Cases", stateFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "atomic_counter",
                                            "Negative Atomic Counter API Cases", atomicCounterFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "shader_image_load",
                                            "Negative Shader Image Load API Cases", imageLoadFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "shader_image_store",
                                            "Negative Shader Image Store API Cases", imageStoreFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "shader_image_atomic",
                                            "Negative Shader Image Atomic API Cases", imageAtomicFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "shader_image_exchange",
                                            "Negative Shader Image Atomic Exchange API Cases",
                                            imageAtomicExchangeFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "shader_function",
                                            "Negative Shader Function Cases", shaderFunctionFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "shader_directive",
                                            "Negative Shader Directive Cases", shaderDirectiveFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "ssbo_block", "Negative SSBO Block Cases",
                                            ssboBlockFuncs));
            host->addChild(
                createChildCases(CASETYPE_CALLBACK, m_context, "precise", "Negative Precise Cases", preciseFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "advanced_blend",
                                            "Negative Advanced Blend Equation Cases", advancedBlendFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "shader_storage",
                                            "Negative Shader Storage Cases", shaderStorageFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "tessellation", "Negative Tessellation Cases",
                                            tessellationFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "oes_sample_variables",
                                            "Negative Sample Variables Cases", sampleVariablesFuncs));
            host->addChild(
                createChildCases(CASETYPE_CALLBACK, m_context, "compute", "Negative Compute Cases", computeFuncs));
            host->addChild(createChildCases(CASETYPE_CALLBACK, m_context, "framebuffer_fetch",
                                            "Negative Framebuffer Fetch Cases", framebufferFetchFuncs));
        }

        {
            tcu::TestCaseGroup *const host =
                new tcu::TestCaseGroup(m_testCtx, "log", "Reporting of standard API errors via log");

            negative->addChild(host);

            host->addChild(
                createChildCases(CASETYPE_LOG, m_context, "buffer", "Negative Buffer API Cases", bufferFuncs));
            host->addChild(
                createChildCases(CASETYPE_LOG, m_context, "texture", "Negative Texture API Cases", textureFuncs));
            host->addChild(
                createChildCases(CASETYPE_LOG, m_context, "shader", "Negative Shader API Cases", shaderFuncs));
            host->addChild(
                createChildCases(CASETYPE_LOG, m_context, "fragment", "Negative Fragment API Cases", fragmentFuncs));
            host->addChild(
                createChildCases(CASETYPE_LOG, m_context, "vertex_array", "Negative Vertex Array API Cases", vaFuncs));
            host->addChild(
                createChildCases(CASETYPE_LOG, m_context, "state", "Negative GL State API Cases", stateFuncs));
            host->addChild(createChildCases(CASETYPE_LOG, m_context, "atomic_counter",
                                            "Negative Atomic Counter API Cases", atomicCounterFuncs));
            host->addChild(createChildCases(CASETYPE_LOG, m_context, "shader_image_load",
                                            "Negative Shader Image Load API Cases", imageLoadFuncs));
            host->addChild(createChildCases(CASETYPE_LOG, m_context, "shader_image_store",
                                            "Negative Shader Image Store API Cases", imageStoreFuncs));
            host->addChild(createChildCases(CASETYPE_LOG, m_context, "shader_image_atomic",
                                            "Negative Shader Image Atomic API Cases", imageAtomicFuncs));
            host->addChild(createChildCases(CASETYPE_LOG, m_context, "shader_image_exchange",
                                            "Negative Shader Image Atomic Exchange API Cases",
                                            imageAtomicExchangeFuncs));
            host->addChild(createChildCases(CASETYPE_LOG, m_context, "shader_function",
                                            "Negative Shader Function Cases", shaderFunctionFuncs));
            host->addChild(createChildCases(CASETYPE_LOG, m_context, "shader_directive",
                                            "Negative Shader Directive Cases", shaderDirectiveFuncs));
            host->addChild(
                createChildCases(CASETYPE_LOG, m_context, "ssbo_block", "Negative SSBO Block Cases", ssboBlockFuncs));
            host->addChild(
                createChildCases(CASETYPE_LOG, m_context, "precise", "Negative Precise Cases", preciseFuncs));
            host->addChild(createChildCases(CASETYPE_LOG, m_context, "advanced_blend",
                                            "Negative Advanced Blend Equation Cases", advancedBlendFuncs));
            host->addChild(createChildCases(CASETYPE_LOG, m_context, "shader_storage", "Negative Shader Storage Cases",
                                            shaderStorageFuncs));
            host->addChild(createChildCases(CASETYPE_LOG, m_context, "tessellation", "Negative Tessellation Cases",
                                            tessellationFuncs));
            host->addChild(createChildCases(CASETYPE_LOG, m_context, "oes_sample_variables",
                                            "Negative Sample Variables Cases", sampleVariablesFuncs));
            host->addChild(
                createChildCases(CASETYPE_LOG, m_context, "compute", "Negative Compute Cases", computeFuncs));
            host->addChild(createChildCases(CASETYPE_LOG, m_context, "framebuffer_fetch",
                                            "Negative Framebuffer Fetch Cases", framebufferFetchFuncs));
        }

        {
            tcu::TestCaseGroup *const host =
                new tcu::TestCaseGroup(m_testCtx, "get_error", "Reporting of standard API errors via glGetError");

            negative->addChild(host);

            host->addChild(
                createChildCases(CASETYPE_GETERROR, m_context, "buffer", "Negative Buffer API Cases", bufferFuncs));
            host->addChild(
                createChildCases(CASETYPE_GETERROR, m_context, "texture", "Negative Texture API Cases", textureFuncs));
            host->addChild(
                createChildCases(CASETYPE_GETERROR, m_context, "shader", "Negative Shader API Cases", shaderFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "fragment", "Negative Fragment API Cases",
                                            fragmentFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "vertex_array",
                                            "Negative Vertex Array API Cases", vaFuncs));
            host->addChild(
                createChildCases(CASETYPE_GETERROR, m_context, "state", "Negative GL State API Cases", stateFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "atomic_counter",
                                            "Negative Atomic Counter API Cases", atomicCounterFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "shader_image_load",
                                            "Negative Shader Image Load API Cases", imageLoadFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "shader_image_store",
                                            "Negative Shader Image Store API Cases", imageStoreFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "shader_image_atomic",
                                            "Negative Shader Image Atomic API Cases", imageAtomicFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "shader_image_exchange",
                                            "Negative Shader Image Atomic Exchange API Cases",
                                            imageAtomicExchangeFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "shader_function",
                                            "Negative Shader Function Cases", shaderFunctionFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "shader_directive",
                                            "Negative Shader Directive Cases", shaderDirectiveFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "ssbo_block", "Negative SSBO Block Cases",
                                            ssboBlockFuncs));
            host->addChild(
                createChildCases(CASETYPE_GETERROR, m_context, "precise", "Negative Precise Cases", preciseFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "advanced_blend",
                                            "Negative Advanced Blend Equation Cases", advancedBlendFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "shader_storage",
                                            "Negative Shader Storage Cases", shaderStorageFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "tessellation", "Negative Tessellation Cases",
                                            tessellationFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "oes_sample_variables",
                                            "Negative Sample Variables Cases", sampleVariablesFuncs));
            host->addChild(
                createChildCases(CASETYPE_GETERROR, m_context, "compute", "Negative Compute Cases", computeFuncs));
            host->addChild(createChildCases(CASETYPE_GETERROR, m_context, "framebuffer_fetch",
                                            "Negative Framebuffer Fetch Cases", framebufferFetchFuncs));
        }
    }

    {
        tcu::TestCaseGroup *const host = createChildCases(CASETYPE_CALLBACK, m_context, "externally_generated",
                                                          "Externally Generated Messages", externalFuncs);

        host->addChild(new GroupCase(m_context, "push_pop_consistency",
                                     "Push/pop message generation with full message output checking"));

        addChild(host);
    }

    {
        vector<FunctionContainer> containers;
        vector<TestFunctionWrapper> allFuncs;

        de::Random rng(0x53941903 ^ m_context.getTestContext().getCommandLine().getBaseSeed());

        containers.insert(containers.end(), bufferFuncs.begin(), bufferFuncs.end());
        containers.insert(containers.end(), textureFuncs.begin(), textureFuncs.end());
        containers.insert(containers.end(), externalFuncs.begin(), externalFuncs.end());

        for (size_t ndx = 0; ndx < containers.size(); ndx++)
            allFuncs.push_back(containers[ndx].function);

        rng.shuffle(allFuncs.begin(), allFuncs.end());

        {
            tcu::TestCaseGroup *const filtering =
                new tcu::TestCaseGroup(m_testCtx, "error_filters", "Filtering of reported errors");
            const int errorFuncsPerCase     = 4;
            const int maxFilteringCaseCount = 32;
            const int caseCount             = (int(allFuncs.size()) + errorFuncsPerCase - 1) / errorFuncsPerCase;

            addChild(filtering);

            for (int caseNdx = 0; caseNdx < de::min(caseCount, maxFilteringCaseCount); caseNdx++)
            {
                const int start   = caseNdx * errorFuncsPerCase;
                const int end     = de::min((caseNdx + 1) * errorFuncsPerCase, int(allFuncs.size()));
                const string name = "case_" + de::toString(caseNdx);
                vector<TestFunctionWrapper> funcs(allFuncs.begin() + start, allFuncs.begin() + end);

                // These produce lots of different message types, thus always include at least one when testing filtering
                funcs.insert(funcs.end(), externalFuncs[caseNdx % externalFuncs.size()].function);

                filtering->addChild(new FilterCase(m_context, name.c_str(), "DebugMessageControl usage", funcs));
            }
        }

        {
            tcu::TestCaseGroup *const groups = new tcu::TestCaseGroup(
                m_testCtx, "error_groups", "Filtering of reported errors with use of Error Groups");
            const int errorFuncsPerCase     = 4;
            const int maxFilteringCaseCount = 16;
            const int caseCount             = (int(allFuncs.size()) + errorFuncsPerCase - 1) / errorFuncsPerCase;

            addChild(groups);

            for (int caseNdx = 0; caseNdx < caseCount && caseNdx < maxFilteringCaseCount; caseNdx++)
            {
                const int start   = caseNdx * errorFuncsPerCase;
                const int end     = de::min((caseNdx + 1) * errorFuncsPerCase, int(allFuncs.size()));
                const string name = ("case_" + de::toString(caseNdx)).c_str();
                vector<TestFunctionWrapper> funcs(&allFuncs[0] + start, &allFuncs[0] + end);

                // These produce lots of different message types, thus always include at least one when testing filtering
                funcs.insert(funcs.end(), externalFuncs[caseNdx % externalFuncs.size()].function);

                groups->addChild(new GroupFilterCase(m_context, name.c_str(), "Debug Group usage", funcs));
            }
        }

        {
            tcu::TestCaseGroup *const async =
                new tcu::TestCaseGroup(m_testCtx, "async", "Asynchronous message generation");
            const int errorFuncsPerCase = 2;
            const int maxAsyncCaseCount = 16;
            const int caseCount         = (int(allFuncs.size()) + errorFuncsPerCase - 1) / errorFuncsPerCase;

            addChild(async);

            for (int caseNdx = 0; caseNdx < caseCount && caseNdx < maxAsyncCaseCount; caseNdx++)
            {
                const int start   = caseNdx * errorFuncsPerCase;
                const int end     = de::min((caseNdx + 1) * errorFuncsPerCase, int(allFuncs.size()));
                const string name = ("case_" + de::toString(caseNdx)).c_str();
                vector<TestFunctionWrapper> funcs(&allFuncs[0] + start, &allFuncs[0] + end);

                if (caseNdx & 0x1)
                    async->addChild(new AsyncCase(m_context, (name + "_callback").c_str(), "Async message generation",
                                                  funcs, true));
                else
                    async->addChild(
                        new AsyncCase(m_context, (name + "_log").c_str(), "Async message generation", funcs, false));
            }
        }
    }

    {
        tcu::TestCaseGroup *const labels = new tcu::TestCaseGroup(m_testCtx, "object_labels", "Labeling objects");

        const struct
        {
            GLenum identifier;
            const char *name;
            const char *desc;
        } cases[] = {
            {GL_BUFFER, "buffer", "Debug label on a buffer object"},
            {GL_SHADER, "shader", "Debug label on a shader object"},
            {GL_PROGRAM, "program", "Debug label on a program object"},
            {GL_QUERY, "query", "Debug label on a query object"},
            {GL_PROGRAM_PIPELINE, "program_pipeline", "Debug label on a program pipeline object"},
            {GL_TRANSFORM_FEEDBACK, "transform_feedback", "Debug label on a transform feedback object"},
            {GL_SAMPLER, "sampler", "Debug label on a sampler object"},
            {GL_TEXTURE, "texture", "Debug label on a texture object"},
            {GL_RENDERBUFFER, "renderbuffer", "Debug label on a renderbuffer object"},
            {GL_FRAMEBUFFER, "framebuffer", "Debug label on a framebuffer object"},
        };

        addChild(labels);

        labels->addChild(new InitialLabelCase(m_context, "initial", "Debug label initial value"));
        labels->addChild(new ClearLabelCase(m_context, "clearing", "Debug label clearing"));
        labels->addChild(
            new SpecifyWithLengthCase(m_context, "specify_with_length", "Debug label specified with length"));
        labels->addChild(
            new BufferLimitedLabelCase(m_context, "buffer_limited_query", "Debug label query to too short buffer"));
        labels->addChild(new LabelMaxSizeCase(m_context, "max_label_length", "Max sized debug label"));
        labels->addChild(new LabelLengthCase(m_context, "query_length_only", "Query debug label length"));

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(cases); ndx++)
            labels->addChild(new LabelCase(m_context, cases[ndx].name, cases[ndx].desc, cases[ndx].identifier));
        labels->addChild(new SyncLabelCase(m_context, "sync", "Debug label on a sync object"));
    }
}

} // namespace Functional
} // namespace gles31
} // namespace deqp
