/*-------------------------------------------------------------------------
 * drawElements Quality Program EGL Module
 * ---------------------------------------
 *
 * Copyright 2017 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 Robustness tests for KHR_robustness.
 *//*--------------------------------------------------------------------*/

#include "teglRobustnessTests.hpp"

#include "tcuTestLog.hpp"
#include "tcuStringTemplate.hpp"

#include "egluConfigFilter.hpp"
#include "egluStrUtil.hpp"
#include "egluUtil.hpp"
#include "eglwLibrary.hpp"

#include "gluStrUtil.hpp"
#include "gluShaderProgram.hpp"
#include "gluDrawUtil.hpp"

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

#include "deSTLUtil.hpp"
#include "deStringUtil.hpp"
#include "deThread.hpp"
#include "deSharedPtr.hpp"

#include <set>

using std::set;
using std::string;
using std::vector;
using tcu::TestLog;

using namespace eglw;

DE_STATIC_ASSERT(GL_RESET_NOTIFICATION_STRATEGY == 0x8256);
DE_STATIC_ASSERT(GL_LOSE_CONTEXT_ON_RESET == 0x8252);
DE_STATIC_ASSERT(GL_NO_RESET_NOTIFICATION == 0x8261);

namespace deqp
{
namespace egl
{
namespace
{

enum ContextResetType
{
    CONTEXTRESETTYPE_SHADER_OOB,
    CONTEXTRESETTYPE_FIXED_FUNC_OOB,
};

enum ShaderType
{
    SHADERTYPE_VERT,
    SHADERTYPE_FRAG,
    SHADERTYPE_COMPUTE,
    SHADERTYPE_VERT_AND_FRAG,
};

enum ReadWriteType
{
    READWRITETYPE_READ,
    READWRITETYPE_WRITE,
};

enum ResourceType
{
    RESOURCETYPE_UBO,
    RESOURCETYPE_SSBO,
    RESOURCETYPE_LOCAL_ARRAY,
};

enum FixedFunctionType
{
    FIXEDFUNCTIONTYPE_INDICES,
    FIXEDFUNCTIONTYPE_VERTICES,
};

enum RobustAccessType
{
    ROBUSTACCESS_TRUE,
    ROBUSTACCESS_FALSE,
};

void requireEGLExtension(const Library &egl, EGLDisplay eglDisplay, const char *requiredExtension)
{
    if (!eglu::hasExtension(egl, eglDisplay, requiredExtension))
        TCU_THROW(NotSupportedError, (string(requiredExtension) + " not supported").c_str());
}

bool isWindow(const eglu::CandidateConfig &c)
{
    return (c.surfaceType() & EGL_WINDOW_BIT) == EGL_WINDOW_BIT;
}

template <uint32_t Type>
bool renderable(const eglu::CandidateConfig &c)
{
    return (c.renderableType() & Type) == Type;
}

eglu::ConfigFilter getRenderableFilter(uint32_t bits)
{
    switch (bits)
    {
    case EGL_OPENGL_ES2_BIT:
        return renderable<EGL_OPENGL_ES2_BIT>;
    case EGL_OPENGL_ES3_BIT:
        return renderable<EGL_OPENGL_ES3_BIT>;
    case EGL_OPENGL_BIT:
        return renderable<EGL_OPENGL_BIT>;
    default:
        DE_FATAL("Unknown EGL bitfied value");
        return renderable<0>;
    }
}

const char *eglResetNotificationStrategyToString(EGLint strategy)
{
    switch (strategy)
    {
    case EGL_NO_RESET_NOTIFICATION_KHR:
        return "EGL_NO_RESET_NOTIFICATION_KHR";
    case EGL_LOSE_CONTEXT_ON_RESET_KHR:
        return "EGL_LOSE_CONTEXT_ON_RESET_KHR";
    default:
        return "<Unknown>";
    }
}

void logAttribList(const EglTestContext &eglTestCtx, const EGLint *attribList)
{
    const EGLint *iter = &(attribList[0]);
    std::ostringstream attribListString;

    while ((*iter) != EGL_NONE)
    {
        switch (*iter)
        {
            //    case EGL_CONTEXT_CLIENT_VERSION:
        case EGL_CONTEXT_MAJOR_VERSION_KHR:
            iter++;
            attribListString << "EGL_CONTEXT_CLIENT_VERSION, " << (*iter) << ", ";
            iter++;
            break;

        case EGL_CONTEXT_MINOR_VERSION_KHR:
            iter++;
            attribListString << "EGL_CONTEXT_MINOR_VERSION_KHR, " << (*iter) << ", ";
            iter++;
            break;

        case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT:
            iter++;
            attribListString << "EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, "
                             << eglResetNotificationStrategyToString(*iter) << ", ";
            iter++;
            break;

        case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR:
            iter++;
            attribListString << "EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, "
                             << eglResetNotificationStrategyToString(*iter) << ", ";
            iter++;
            break;

        case EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT:
            iter++;
            attribListString << "EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, ";

            if (*iter == EGL_FALSE || *iter == EGL_TRUE)
                attribListString << (*iter ? "EGL_TRUE" : "EGL_FALSE") << ", ";
            else
                attribListString << (*iter) << ", ";
            iter++;
            break;

        default:
            DE_FATAL("Unsupported attribute");
        }
    }

    attribListString << "EGL_NONE";
    eglTestCtx.getTestContext().getLog() << TestLog::Message << "EGL attrib list: { " << attribListString.str()
                                         << " }\n\n"
                                         << TestLog::EndMessage;
}

class RobustnessTestCase : public TestCase
{
public:
    class Params
    {
    public:
        Params(void)
        {
        }

        Params(const string &name, const string &description, const RobustAccessType &robustAccessType,
               const ContextResetType &contextResetType, const FixedFunctionType &fixedFunctionType);

        Params(const string &name, const string &description, const RobustAccessType &robustAccessType,
               const ContextResetType &contextResetType, const ShaderType &shaderType, const ResourceType &resourceType,
               const ReadWriteType &readWriteType);

        const string &getName(void) const
        {
            return m_name;
        }
        const string &getDescription(void) const
        {
            return m_description;
        }
        const ContextResetType &getContextResetType(void) const
        {
            return m_contextResetType;
        }
        const ShaderType &getShaderType(void) const
        {
            return m_shaderType;
        }
        const ResourceType &getResourceType(void) const
        {
            return m_resourceType;
        }
        const ReadWriteType &getReadWriteType(void) const
        {
            return m_readWriteType;
        }
        const FixedFunctionType &getFixedFunctionType(void) const
        {
            return m_fixedFunctionType;
        }
        const RobustAccessType &getRobustAccessType(void) const
        {
            return m_robustAccessType;
        }

    private:
        string m_name;
        string m_description;
        RobustAccessType m_robustAccessType;
        ContextResetType m_contextResetType;
        ShaderType m_shaderType;
        ResourceType m_resourceType;
        ReadWriteType m_readWriteType;
        FixedFunctionType m_fixedFunctionType;
    };

    RobustnessTestCase(EglTestContext &eglTestCtx, const char *name, const char *description);
    RobustnessTestCase(EglTestContext &eglTestCtx, const char *name, const char *description, Params params);
    ~RobustnessTestCase(void);

    void checkRequiredEGLExtensions(const EGLint *attribList);

protected:
    Params m_params;
    EGLDisplay m_eglDisplay;
    EGLConfig m_eglConfig;
    EGLSurface m_eglSurface;

private:
    void init(void);
    void deinit(void);
    void initEGLSurface(void);
    EGLConfig getEGLConfig(void);

    eglu::NativeWindow *m_window;
};

RobustnessTestCase::Params::Params(const string &name, const string &description,
                                   const RobustAccessType &robustAccessType, const ContextResetType &contextResetType,
                                   const FixedFunctionType &fixedFunctionType)
    : m_name(name)
    , m_description(description)
    , m_robustAccessType(robustAccessType)
    , m_contextResetType(contextResetType)
    , m_fixedFunctionType(fixedFunctionType)
{
}

RobustnessTestCase::Params::Params(const string &name, const string &description,
                                   const RobustAccessType &robustAccessType, const ContextResetType &contextResetType,
                                   const ShaderType &shaderType, const ResourceType &resourceType,
                                   const ReadWriteType &readWriteType)
    : m_name(name)
    , m_description(description)
    , m_robustAccessType(robustAccessType)
    , m_contextResetType(contextResetType)
    , m_shaderType(shaderType)
    , m_resourceType(resourceType)
    , m_readWriteType(readWriteType)
{
}

RobustnessTestCase::RobustnessTestCase(EglTestContext &eglTestCtx, const char *name, const char *description)
    : TestCase(eglTestCtx, name, description)
    , m_eglDisplay(EGL_NO_DISPLAY)
    , m_eglConfig(0)
    , m_eglSurface(EGL_NO_SURFACE)
    , m_window(DE_NULL)
{
}

RobustnessTestCase::RobustnessTestCase(EglTestContext &eglTestCtx, const char *name, const char *description,
                                       Params params)
    : TestCase(eglTestCtx, name, description)
    , m_params(params)
    , m_eglDisplay(EGL_NO_DISPLAY)
    , m_eglConfig(0)
    , m_eglSurface(EGL_NO_SURFACE)
    , m_window(DE_NULL)
{
}

RobustnessTestCase::~RobustnessTestCase(void)
{
    deinit();
}

void RobustnessTestCase::init(void)
{
    m_eglDisplay = eglu::getAndInitDisplay(m_eglTestCtx.getNativeDisplay());
    m_eglConfig  = getEGLConfig();

    initEGLSurface();
}

void RobustnessTestCase::deinit(void)
{
    const Library &egl = m_eglTestCtx.getLibrary();

    if (m_eglSurface != EGL_NO_SURFACE)
    {
        egl.destroySurface(m_eglDisplay, m_eglSurface);
        m_eglSurface = EGL_NO_SURFACE;
    }
    if (m_eglDisplay != EGL_NO_DISPLAY)
    {
        egl.terminate(m_eglDisplay);
        m_eglDisplay = EGL_NO_DISPLAY;
    }

    delete m_window;
    m_window = DE_NULL;
}

EGLConfig RobustnessTestCase::getEGLConfig(void)
{
    eglu::FilterList filters;
    filters << isWindow << getRenderableFilter(EGL_OPENGL_ES3_BIT);
    return eglu::chooseSingleConfig(m_eglTestCtx.getLibrary(), m_eglDisplay, filters);
}

void RobustnessTestCase::initEGLSurface(void)
{
    EGLU_CHECK_CALL(m_eglTestCtx.getLibrary(), bindAPI(EGL_OPENGL_ES_API));

    const eglu::NativeWindowFactory &factory =
        eglu::selectNativeWindowFactory(m_eglTestCtx.getNativeDisplayFactory(), m_testCtx.getCommandLine());

    const eglu::WindowParams windowParams =
        eglu::WindowParams(256, 256, eglu::parseWindowVisibility(m_testCtx.getCommandLine()));
    m_window = factory.createWindow(&m_eglTestCtx.getNativeDisplay(), m_eglDisplay, m_eglConfig, DE_NULL, windowParams);
    m_eglSurface =
        eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *m_window, m_eglDisplay, m_eglConfig, DE_NULL);
}

glu::ApiType paramsToApiType(const RobustnessTestCase::Params &params)
{
    EGLint minorVersion = 0;
    if (params.getShaderType() == SHADERTYPE_COMPUTE || params.getResourceType() == RESOURCETYPE_SSBO ||
        params.getContextResetType() == CONTEXTRESETTYPE_SHADER_OOB)
    {
        minorVersion = 1;
    }

    return glu::ApiType::es(3, minorVersion);
}

void RobustnessTestCase::checkRequiredEGLExtensions(const EGLint *attribList)
{
    set<string> requiredExtensions;
    vector<string> extensions = eglu::getDisplayExtensions(m_eglTestCtx.getLibrary(), m_eglDisplay);

    {
        const EGLint *iter = attribList;

        while ((*iter) != EGL_NONE)
        {
            switch (*iter)
            {
            case EGL_CONTEXT_MAJOR_VERSION_KHR:
                iter++;
                iter++;
                break;

            case EGL_CONTEXT_MINOR_VERSION_KHR:
                iter++;
                requiredExtensions.insert("EGL_KHR_create_context");
                iter++;
                break;

            case EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT:
            case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT:
                iter++;
                requiredExtensions.insert("EGL_EXT_create_context_robustness");
                iter++;
                break;

            default:
                DE_ASSERT(false);
            }
        }
    }

    for (std::set<string>::const_iterator reqExt = requiredExtensions.begin(); reqExt != requiredExtensions.end();
         ++reqExt)
    {
        if (!de::contains(extensions.begin(), extensions.end(), *reqExt))
        {
            const char *const extension = reqExt->c_str();
            requireEGLExtension(m_eglTestCtx.getLibrary(), m_eglDisplay, extension);
        }
    }
}

void checkRequiredGLSupport(const glw::Functions &gl, glu::ApiType requiredApi)
{
    if (!glu::hasExtension(gl, requiredApi, "GL_KHR_robustness") &&
        !glu::hasExtension(gl, requiredApi, "GL_EXT_robustness"))
    {
        TCU_THROW(NotSupportedError, (string("GL_KHR_robustness and GL_EXT_robustness") + " not supported").c_str());
    }
    else
    {
        int realMinorVersion = 0;
        gl.getIntegerv(GL_MINOR_VERSION, &realMinorVersion);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Get minor version failed");

        if (realMinorVersion < requiredApi.getMinorVersion())
            TCU_THROW(NotSupportedError, "Test case requires GLES 3.1");
    }
}

void checkGLSupportForParams(const glw::Functions &gl, const RobustnessTestCase::Params &params)
{
    int minorVersion = 0;
    if (params.getShaderType() == SHADERTYPE_COMPUTE || params.getResourceType() == RESOURCETYPE_SSBO ||
        params.getContextResetType() == CONTEXTRESETTYPE_SHADER_OOB)
    {
        minorVersion = 1;
    }
    checkRequiredGLSupport(gl, glu::ApiType::es(3, minorVersion));
}

class RenderingContext
{
public:
    RenderingContext(const EglTestContext &eglTestCtx, const EGLint *attribList, const EGLConfig &config,
                     const EGLDisplay &display, const EGLContext &sharedContext);
    ~RenderingContext(void);

    void initGLFunctions(glw::Functions *gl, const glu::ApiType apiType);
    void makeCurrent(const EGLSurface &surface);
    EGLContext getContext(void);

private:
    const EglTestContext &m_eglTestCtx;
    const EGLint *m_attribList;
    const EGLConfig &m_config;
    const EGLDisplay &m_display;
    const Library &m_egl;

    EGLContext m_context;

    void createContext(const EGLConfig &sharedConfig);
    void destroyContext(void);

    RenderingContext(const RenderingContext &);
    RenderingContext &operator=(const RenderingContext &);
};

RenderingContext::RenderingContext(const EglTestContext &eglTestCtx, const EGLint *attribList, const EGLConfig &config,
                                   const EGLDisplay &display, const EGLContext &sharedContext)
    : m_eglTestCtx(eglTestCtx)
    , m_attribList(attribList)
    , m_config(config)
    , m_display(display)
    , m_egl(eglTestCtx.getLibrary())
    , m_context(EGL_NO_CONTEXT)
{
    logAttribList(eglTestCtx, m_attribList);
    createContext(sharedContext);
}

RenderingContext::~RenderingContext(void)
{
    destroyContext();
}

void RenderingContext::createContext(const EGLConfig &sharedContext)
{
    m_context = m_egl.createContext(m_display, m_config, sharedContext, m_attribList);
    EGLU_CHECK_MSG(m_egl, "eglCreateContext()");
}

void RenderingContext::destroyContext(void)
{
    EGLU_CHECK_CALL(m_eglTestCtx.getLibrary(), makeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));

    if (m_context != EGL_NO_CONTEXT)
        m_egl.destroyContext(m_display, m_context);
}

void RenderingContext::makeCurrent(const EGLSurface &surface)
{
    EGLU_CHECK_CALL(m_egl, makeCurrent(m_display, surface, surface, m_context));
}

void RenderingContext::initGLFunctions(glw::Functions *gl, const glu::ApiType apiType)
{
    // \todo [2017-03-23 pyry] Current version has 2 somewhat ugly hacks:
    //
    // 1) Core functions are loaded twice. We need glGetString(i) to query supported
    //    extensions to determine if we need to load EXT or KHR-suffixed robustness
    //    functions. This could be fixed by exposing glw::FunctionLoader in EglTestContext
    //    for example.
    //
    // 2) We assume that calling code will check for KHR_robustness or EXT_robustness
    //    support after calling initGLFunctions(). We could move the check here.

    m_eglTestCtx.initGLFunctions(gl, apiType);

    {
        const char *const robustnessExt =
            glu::hasExtension(*gl, apiType, "GL_KHR_robustness") ? "GL_KHR_robustness" : "GL_EXT_robustness";
        const char *const extensions[] = {robustnessExt};

        m_eglTestCtx.initGLFunctions(gl, apiType, DE_LENGTH_OF_ARRAY(extensions), &extensions[0]);
    }
}

EGLContext RenderingContext::getContext(void)
{
    return m_context;
}

class ContextReset
{
public:
    ContextReset(glw::Functions &gl, tcu::TestLog &log, FixedFunctionType fixedFunctionType);
    ContextReset(glw::Functions &gl, tcu::TestLog &log, ShaderType shaderType, ResourceType resourceType,
                 ReadWriteType readWriteType);

    virtual ~ContextReset(void)
    {
    }

    virtual void setup(void)    = 0;
    virtual void draw(void)     = 0;
    virtual void teardown(void) = 0;

    void finish(void);

    glw::GLint getError(void);
    glw::GLint getGraphicsResetStatus(void);

    glw::GLsync getSyncObject(void) const
    {
        return m_sync;
    }
    glw::GLuint getQueryID(void) const
    {
        return m_queryID;
    }

    glw::Functions &m_gl;
    tcu::TestLog &m_log;
    ShaderType m_shaderType;
    ResourceType m_resourceType;
    ReadWriteType m_readWriteType;
    FixedFunctionType m_fixedFunctionType;

private:
    ContextReset(const ContextReset &);
    ContextReset &operator=(const ContextReset &);

    glw::GLuint m_queryID;
    glw::GLsync m_sync;
};

ContextReset::ContextReset(glw::Functions &gl, tcu::TestLog &log, FixedFunctionType fixedFunctionType)
    : m_gl(gl)
    , m_log(log)
    , m_fixedFunctionType(fixedFunctionType)
{
}

ContextReset::ContextReset(glw::Functions &gl, tcu::TestLog &log, ShaderType shaderType, ResourceType resourceType,
                           ReadWriteType readWriteType)
    : m_gl(gl)
    , m_log(log)
    , m_shaderType(shaderType)
    , m_resourceType(resourceType)
    , m_readWriteType(readWriteType)
{
}

void ContextReset::finish(void)
{
    GLU_CHECK_GLW_CALL(m_gl, finish());
}

glw::GLint ContextReset::getError(void)
{
    glw::GLint error;
    error = m_gl.getError();

    return error;
}

glw::GLint ContextReset::getGraphicsResetStatus(void)
{
    glw::GLint resetStatus;
    resetStatus = m_gl.getGraphicsResetStatus();

    return resetStatus;
}

class FixedFunctionOOB : public ContextReset
{
public:
    FixedFunctionOOB(glw::Functions &gl, tcu::TestLog &log, FixedFunctionType fixedFunctionType);
    ~FixedFunctionOOB(void);

    struct TestConfig
    {
        int textureWidth;
        int textureHeight;
    };

    virtual void setup(void);
    virtual void draw(void);
    virtual void teardown(void);

private:
    glu::ProgramSources genSources(void);
    glw::GLuint m_coordinatesBuffer;
    glw::GLint m_coordLocation;
};

FixedFunctionOOB::FixedFunctionOOB(glw::Functions &gl, tcu::TestLog &log, FixedFunctionType fixedFunctionType)
    : ContextReset(gl, log, fixedFunctionType)
    , m_coordinatesBuffer(0)
    , m_coordLocation(0)
{
}

FixedFunctionOOB::~FixedFunctionOOB(void)
{
    try
    {
        // Reset GL_CONTEXT_LOST error before destroying resources
        m_gl.getGraphicsResetStatus();
        teardown();
    }
    catch (...)
    {
        // Ignore GL errors from teardown()
    }
}

glu::ProgramSources FixedFunctionOOB::genSources(void)
{
    const char *const vert = "#version 300 es\n"
                             "in highp vec4 a_position;\n"
                             "void main (void)\n"
                             "{\n"
                             "    gl_Position = a_position;\n"
                             "}\n";

    const char *const frag = "#version 300 es\n"
                             "layout(location = 0) out highp vec4 fragColor;\n"
                             "void main (void)\n"
                             "{\n"
                             "    fragColor = vec4(1.0f);\n"
                             "}\n";

    return glu::ProgramSources() << glu::VertexSource(vert) << glu::FragmentSource(frag);
}

void FixedFunctionOOB::setup(void)
{
    glu::ShaderProgram program(m_gl, genSources());

    m_log << program;

    if (!program.isOk())
        TCU_FAIL("Failed to compile shader program");

    GLU_CHECK_GLW_CALL(m_gl, useProgram(program.getProgram()));

    const glw::GLfloat coords[] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f};

    m_coordLocation = m_gl.getAttribLocation(program.getProgram(), "a_position");
    GLU_CHECK_GLW_MSG(m_gl, "glGetAttribLocation()");
    TCU_CHECK(m_coordLocation != (glw::GLint)-1);

    // Load the vertex data
    m_coordinatesBuffer = 0;
    GLU_CHECK_GLW_CALL(m_gl, genBuffers(1, &m_coordinatesBuffer));
    GLU_CHECK_GLW_CALL(m_gl, bindBuffer(GL_ARRAY_BUFFER, m_coordinatesBuffer));
    GLU_CHECK_GLW_CALL(m_gl, bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)sizeof(coords), coords, GL_STATIC_DRAW));
    GLU_CHECK_GLW_CALL(m_gl, enableVertexAttribArray(m_coordLocation));
    GLU_CHECK_GLW_CALL(m_gl, vertexAttribPointer(m_coordLocation, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL));
}

void FixedFunctionOOB::draw(void)
{
    const glw::GLint bad_indices[] = {0, 10, 100, 1000, 10000, 100000};

    if (m_fixedFunctionType == FIXEDFUNCTIONTYPE_INDICES)
        m_gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, bad_indices);
    else if (m_fixedFunctionType == FIXEDFUNCTIONTYPE_VERTICES)
        m_gl.drawArrays(GL_TRIANGLES, 0, 1000);
    else
        DE_FATAL("Unknown fixed function type");
}

void FixedFunctionOOB::teardown(void)
{
    if (m_coordLocation)
    {
        GLU_CHECK_GLW_CALL(m_gl, disableVertexAttribArray(m_coordLocation));
        m_coordLocation = 0;
    }

    if (m_coordinatesBuffer)
    {
        GLU_CHECK_GLW_CALL(m_gl, deleteBuffers(1, &m_coordinatesBuffer));
        m_coordinatesBuffer = 0;
    }

    GLU_CHECK_GLW_CALL(m_gl, useProgram(0));
}

class ShadersOOB : public ContextReset
{
public:
    ShadersOOB(glw::Functions &gl, tcu::TestLog &log, ShaderType shaderType, ResourceType resourceType,
               ReadWriteType readWriteType);
    ~ShadersOOB(void);

    virtual void setup(void);
    virtual void draw(void);
    virtual void teardown(void);

private:
    static const int s_numBindings = 3;

    glw::GLuint m_coordinatesBuffer;
    glw::GLint m_coordLocation;

    bool m_isUBO;
    bool m_isRead;
    bool m_isLocalArray;
    std::vector<glw::GLuint> m_buffers;

    std::string genVertexShader(const std::string &shaderDecl, const std::string &shaderBody);
    std::string genFragmentShader(const std::string &shaderDecl, const std::string &shaderBody);
    std::string genComputeShader(const std::string &shaderDecl, const std::string &shaderBody);

    glu::ProgramSources genNonComputeSource(void);
    glu::ProgramSources genComputeSource(void);
    glu::ProgramSources genSources(void);
};

ShadersOOB::ShadersOOB(glw::Functions &gl, tcu::TestLog &log, ShaderType shaderType, ResourceType resourceType,
                       ReadWriteType readWriteType)
    : ContextReset(gl, log, shaderType, resourceType, readWriteType)
    , m_coordinatesBuffer(0)
    , m_coordLocation(0)
    , m_buffers(s_numBindings, 0)
{
    m_isUBO        = (m_resourceType == RESOURCETYPE_UBO);
    m_isLocalArray = (m_resourceType == RESOURCETYPE_LOCAL_ARRAY);
    m_isRead       = (m_readWriteType == READWRITETYPE_READ);
}

ShadersOOB::~ShadersOOB(void)
{
    try
    {
        // Reset GL_CONTEXT_LOST error before destroying resources
        m_gl.getGraphicsResetStatus();
        teardown();
    }
    catch (...)
    {
        // Ignore GL errors from teardown()
    }
}

std::string ShadersOOB::genVertexShader(const std::string &shaderDecl, const std::string &shaderBody)
{
    static const char *const s_simpleVertexShaderSource = "#version 310 es\n"
                                                          "in highp vec4 a_position;\n"
                                                          "void main (void)\n"
                                                          "{\n"
                                                          "    gl_Position = a_position;\n"
                                                          "}\n";

    switch (m_shaderType)
    {
    case SHADERTYPE_VERT:
    case SHADERTYPE_VERT_AND_FRAG:
    {
        std::ostringstream vertexShaderSource;
        vertexShaderSource << "#version 310 es\n"
                           << "in highp vec4 a_position;\n"
                           << "out highp vec4 v_color;\n"
                           << shaderDecl << "\n"
                           << "void main (void)\n"
                           << "{\n"
                           << "    highp vec4 color = vec4(0.0f);\n"
                           << shaderBody << "\n"
                           << "    v_color = color;\n"
                           << "    gl_Position = a_position;\n"
                           << "}\n";

        return vertexShaderSource.str();
    }

    case SHADERTYPE_FRAG:
        return s_simpleVertexShaderSource;

    default:
        DE_FATAL("Unknown shader type");
        return "";
    }
}

std::string ShadersOOB::genFragmentShader(const std::string &shaderDecl, const std::string &shaderBody)
{
    static const char *const s_simpleFragmentShaderSource = "#version 310 es\n"
                                                            "in highp vec4 v_color;\n"
                                                            "layout(location = 0) out highp vec4 fragColor;\n"
                                                            "void main (void)\n"
                                                            "{\n"
                                                            "    fragColor = v_color;\n"
                                                            "}\n";

    switch (m_shaderType)
    {
    case SHADERTYPE_VERT:
        return s_simpleFragmentShaderSource;

    case SHADERTYPE_FRAG:
    {
        std::ostringstream fragmentShaderSource;
        fragmentShaderSource << "#version 310 es\n"
                             << "layout(location = 0) out highp vec4 fragColor;\n"
                             << shaderDecl << "\n"
                             << "void main (void)\n"
                             << "{\n"
                             << "    highp vec4 color = vec4(0.0f);\n"
                             << shaderBody << "\n"
                             << "    fragColor = color;\n"
                             << "}\n";

        return fragmentShaderSource.str();
    }
    case SHADERTYPE_VERT_AND_FRAG:
    {
        std::ostringstream fragmentShaderSource;
        fragmentShaderSource << "#version 310 es\n"
                             << "in highp vec4 v_color;\n"
                             << "layout(location = 0) out highp vec4 fragColor;\n"
                             << shaderDecl << "\n"
                             << "void main (void)\n"
                             << "{\n"
                             << "    highp vec4 color = vec4(0.0f);\n"
                             << shaderBody << "\n"
                             << "    fragColor = color;\n"
                             << "}\n";

        return fragmentShaderSource.str();
    }

    default:
        DE_FATAL("Unknown shader type");
        return "";
    }
}

std::string ShadersOOB::genComputeShader(const std::string &shaderDecl, const std::string &shaderBody)
{
    std::ostringstream computeShaderSource;

    computeShaderSource << "#version 310 es\n"
                        << "layout(local_size_x = 1, local_size_y = 1) in;\n"
                        << "\n"
                        << "layout(binding = 0) buffer Output {\n"
                        << "    highp vec4 values;\n"
                        << "} sb_out;\n"
                        << "\n"
                        << shaderDecl << "void main ()\n"
                        << "{\n"
                        << shaderBody << "}\n";

    return computeShaderSource.str();
}

glu::ProgramSources ShadersOOB::genNonComputeSource(void)
{
    std::ostringstream shaderDecl;
    std::ostringstream shaderBody;

    shaderDecl << "uniform highp int u_index;\n";

    if (m_isLocalArray)
    {
        const char *const readWriteStatement =
            (m_isRead) ? "    color.x = color_out[u_index];\n" : "    color[u_index] = color_out[0];\n";

        shaderBody << "    highp float color_out[4] = float[4](0.25f, 0.5f, 0.75f, 1.0f);\n" << readWriteStatement;
    }
    else
    {
        const std::string resName = (m_isUBO) ? "ub_in" : "sb_in";

        shaderDecl << "layout(std140, binding = 0) " << ((m_isUBO) ? "uniform" : "buffer") << " Block\n"
                   << "{\n"
                   << "    highp float color_out[4];\n"
                   << "} " << resName << "[" << s_numBindings << "];\n";

        const std::string readWriteStatement = (m_isRead) ? "    color.x = " + resName + "[0].color_out[u_index];\n" :
                                                            "    color[u_index] = " + resName + "[0].color_out[0];\n";

        shaderBody << readWriteStatement;
    }

    return glu::ProgramSources() << glu::VertexSource(genVertexShader(shaderDecl.str(), shaderBody.str()))
                                 << glu::FragmentSource(genFragmentShader(shaderDecl.str(), shaderBody.str()));
}

glu::ProgramSources ShadersOOB::genComputeSource(void)
{
    std::ostringstream shaderDecl;
    std::ostringstream shaderBody;

    shaderDecl << "uniform highp int u_index;\n";

    shaderBody << "    uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
               << "    uint groupNdx = size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n";

    if (m_isLocalArray)
    {
        const char *const readWriteStatement =
            (m_isRead) ? "    sb_out.values.x = values[u_index];\n" : "    sb_out.values[u_index] = values.x;\n";

        shaderBody << "    highp vec4 values = vec4(1.0f, 0.0f, 3.0f, 2.0f) * float(groupNdx);\n" << readWriteStatement;
    }
    else
    {
        const std::string resName = (m_isUBO) ? "ub_in" : "sb_in";

        shaderDecl << "layout(std140, binding = 1) " << ((m_isUBO) ? "uniform" : "buffer") << " Input\n"
                   << "{\n"
                   << "    highp vec4 values;\n"
                   << "} " << resName << "[" << s_numBindings << "];\n";

        std::string readWriteStatement =
            (m_isRead) ? "    sb_out.values.x = " + resName + "[0].values[u_index] * float(groupNdx);\n" :
                         "    sb_out.values[u_index] = " + resName + "[0].values.x * float(groupNdx);\n";

        shaderBody << readWriteStatement;
    }

    return glu::ProgramSources() << glu::ComputeSource(genComputeShader(shaderDecl.str(), shaderBody.str()));
}

glu::ProgramSources ShadersOOB::genSources(void)
{
    if (m_shaderType == SHADERTYPE_COMPUTE)
        return genComputeSource();
    else
        return genNonComputeSource();
}

void ShadersOOB::setup(void)
{
    if (!m_isUBO && !m_isLocalArray && (m_shaderType != SHADERTYPE_COMPUTE))
    {
        // Check implementation limits for shader SSBO
        int shaderStorageBlockSupported = -1;
        const bool isVertex =
            (m_shaderType == SHADERTYPE_VERT || m_shaderType == SHADERTYPE_VERT_AND_FRAG) ? true : false;
        string shaderTypeStr =
            isVertex ? "GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS" : "GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS";

        GLU_CHECK_GLW_CALL(
            m_gl, getIntegerv(isVertex ? GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS : GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS,
                              &shaderStorageBlockSupported));

        if (shaderStorageBlockSupported < (int)m_buffers.size())
            TCU_THROW(NotSupportedError,
                      ("Test requires " + shaderTypeStr + " >= " + de::toString((int)m_buffers.size()) + ", got " +
                       de::toString(shaderStorageBlockSupported))
                          .c_str());
    }

    glu::ShaderProgram program(m_gl, genSources());

    m_log << program;

    if (!program.isOk())
        TCU_FAIL("Failed to compile shader program");

    GLU_CHECK_GLW_CALL(m_gl, useProgram(program.getProgram()));

    const glw::GLint indexLocation = m_gl.getUniformLocation(program.getProgram(), "u_index");
    GLU_CHECK_GLW_MSG(m_gl, "glGetUniformLocation()");
    TCU_CHECK(indexLocation != (glw::GLint)-1);

    const glw::GLint index = -1;
    GLU_CHECK_GLW_CALL(m_gl, uniform1i(indexLocation, index));

    if (m_shaderType != SHADERTYPE_COMPUTE)
    {
        const glw::GLfloat coords[] = {-1.0f, -1.0f, +1.0f, -1.0f, +1.0f, +1.0f, -1.0f, +1.0f};

        // Setup vertices position
        m_coordLocation = m_gl.getAttribLocation(program.getProgram(), "a_position");
        GLU_CHECK_GLW_MSG(m_gl, "glGetAttribLocation()");
        TCU_CHECK(m_coordLocation != (glw::GLint)-1);

        // Load the vertex data
        m_coordinatesBuffer = 0;
        GLU_CHECK_GLW_CALL(m_gl, genBuffers(1, &m_coordinatesBuffer));
        GLU_CHECK_GLW_CALL(m_gl, bindBuffer(GL_ARRAY_BUFFER, m_coordinatesBuffer));
        GLU_CHECK_GLW_CALL(m_gl, bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)sizeof(coords), coords, GL_STATIC_DRAW));
        GLU_CHECK_GLW_CALL(m_gl, enableVertexAttribArray(m_coordLocation));
        GLU_CHECK_GLW_CALL(m_gl, vertexAttribPointer(m_coordLocation, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL));
    }

    // Create unused data for filling buffer objects
    const std::vector<tcu::Vec4> refValues(s_numBindings, tcu::Vec4(0.0f, 1.0f, 1.0f, 1.0f));

    if (m_isLocalArray && m_shaderType == SHADERTYPE_COMPUTE)
    {
        // Setup output buffer
        GLU_CHECK_GLW_CALL(m_gl, genBuffers((glw::GLsizei)1u, &m_buffers[0]));

        GLU_CHECK_GLW_CALL(m_gl, bindBuffer(GL_SHADER_STORAGE_BUFFER, m_buffers[0]));
        GLU_CHECK_GLW_CALL(m_gl,
                           bufferData(GL_SHADER_STORAGE_BUFFER, sizeof(tcu::Vec4), &(refValues[0]), GL_STATIC_DRAW));
        GLU_CHECK_GLW_CALL(m_gl, bindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_buffers[0]));
    }
    else if (!m_isLocalArray)
    {
        // Set up interface block of buffer bindings
        GLU_CHECK_GLW_CALL(m_gl, genBuffers((glw::GLsizei)m_buffers.size(), &m_buffers[0]));

        for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
        {
            const glw::GLenum resType = m_isUBO && (m_shaderType != SHADERTYPE_COMPUTE || bufNdx != 0) ?
                                            GL_UNIFORM_BUFFER :
                                            GL_SHADER_STORAGE_BUFFER;

            GLU_CHECK_GLW_CALL(m_gl, bindBuffer(resType, m_buffers[bufNdx]));
            GLU_CHECK_GLW_CALL(m_gl, bufferData(resType, sizeof(tcu::Vec4), &(refValues[bufNdx]), GL_STATIC_DRAW));
            GLU_CHECK_GLW_CALL(m_gl, bindBufferBase(resType, bufNdx, m_buffers[bufNdx]));
        }
    }
}

void ShadersOOB::draw(void)
{
    if (m_shaderType == SHADERTYPE_COMPUTE)
        m_gl.dispatchCompute(1, 1, 1);
    else
    {
        const glw::GLuint indices[] = {0, 1, 2, 2, 3, 0};
        m_gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, indices);
    }
}

void ShadersOOB::teardown(void)
{
    if (m_shaderType != SHADERTYPE_COMPUTE)
    {
        if (m_coordLocation)
        {
            GLU_CHECK_GLW_CALL(m_gl, disableVertexAttribArray(m_coordLocation));
            m_coordLocation = 0;
        }
    }

    if (m_coordinatesBuffer)
    {
        GLU_CHECK_GLW_CALL(m_gl, deleteBuffers(1, &m_coordinatesBuffer));
        m_coordinatesBuffer = 0;
    }

    if (!m_isLocalArray)
    {
        if (!m_buffers.empty())
        {
            GLU_CHECK_GLW_CALL(m_gl, deleteBuffers((glw::GLsizei)m_buffers.size(), &m_buffers[0]));
            m_buffers.clear();
        }
    }

    GLU_CHECK_GLW_CALL(m_gl, useProgram(0));
}

class QueryRobustAccessCase : public RobustnessTestCase
{
public:
    QueryRobustAccessCase(EglTestContext &eglTestCtx, const char *name, const char *description)
        : RobustnessTestCase(eglTestCtx, name, description)
    {
    }

    TestCase::IterateResult iterate(void)
    {
        TestLog &log = m_testCtx.getLog();

        log << tcu::TestLog::Message
            << "Check that after successfully creating a robust context the robust access query returned by "
               "glBooleanv() equals GL_TRUE\n\n"
            << tcu::TestLog::EndMessage;

        const EGLint attribList[] = {EGL_CONTEXT_CLIENT_VERSION,
                                     3,
                                     EGL_CONTEXT_MINOR_VERSION_KHR,
                                     0,
                                     EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT,
                                     EGL_TRUE,
                                     EGL_NONE};

        checkRequiredEGLExtensions(attribList);

        RenderingContext context(m_eglTestCtx, attribList, m_eglConfig, m_eglDisplay, EGL_NO_CONTEXT);
        context.makeCurrent(m_eglSurface);

        glw::Functions gl;
        {
            const glu::ApiType apiType(3, 0, glu::PROFILE_ES);
            context.initGLFunctions(&gl, apiType);
            checkRequiredGLSupport(gl, apiType);
        }

        uint8_t robustAccessGL;
        gl.getBooleanv(GL_CONTEXT_ROBUST_ACCESS_EXT, &robustAccessGL);
        GLU_EXPECT_NO_ERROR(gl.getError(), "glGetBooleanv()");

        if (robustAccessGL != GL_TRUE)
        {
            log << TestLog::Message << "Invalid GL_CONTEXT_ROBUST_ACCESS returned by glGetBooleanv(). Got '"
                << robustAccessGL << "' expected GL_TRUE." << TestLog::EndMessage;

            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
            return STOP;
        }

        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        return STOP;
    }
};

class NoResetNotificationCase : public RobustnessTestCase
{
public:
    NoResetNotificationCase(EglTestContext &eglTestCtx, const char *name, const char *description)
        : RobustnessTestCase(eglTestCtx, name, description)
    {
    }

    TestCase::IterateResult iterate(void)
    {
        TestLog &log = m_testCtx.getLog();

        log << tcu::TestLog::Message
            << "Check the reset notification strategy returned by glGetIntegerv() equals GL_NO_RESET_NOTIFICATION\n\n"
            << tcu::TestLog::EndMessage;

        const EGLint attribList[] = {EGL_CONTEXT_CLIENT_VERSION,
                                     3,
                                     EGL_CONTEXT_MINOR_VERSION_KHR,
                                     0,
                                     EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT,
                                     EGL_TRUE,
                                     EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT,
                                     EGL_NO_RESET_NOTIFICATION,
                                     EGL_NONE};

        checkRequiredEGLExtensions(attribList);

        RenderingContext context(m_eglTestCtx, attribList, m_eglConfig, m_eglDisplay, EGL_NO_CONTEXT);
        context.makeCurrent(m_eglSurface);

        glw::Functions gl;
        {
            const glu::ApiType apiType(3, 0, glu::PROFILE_ES);
            context.initGLFunctions(&gl, apiType);
            checkRequiredGLSupport(gl, apiType);
        }

        uint8_t robustAccessGL;
        gl.getBooleanv(GL_CONTEXT_ROBUST_ACCESS_EXT, &robustAccessGL);
        GLU_EXPECT_NO_ERROR(gl.getError(), "glGetBooleanv()");

        glw::GLint reset = 0;
        gl.getIntegerv(GL_RESET_NOTIFICATION_STRATEGY, &reset);
        GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");

        if (reset != GL_NO_RESET_NOTIFICATION)
        {
            log << tcu::TestLog::Message << "Test failed! glGetIntegerv() returned wrong value. ["
                << glu::getErrorStr(reset) << ", expected " << glu::getErrorStr(GL_NO_RESET_NOTIFICATION) << "]"
                << tcu::TestLog::EndMessage;

            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
            return STOP;
        }

        GLU_CHECK_GLW_CALL(gl, getGraphicsResetStatus());

        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        return STOP;
    }
};

class LoseContextOnResetCase : public RobustnessTestCase
{
public:
    LoseContextOnResetCase(EglTestContext &eglTestCtx, const char *name, const char *description)
        : RobustnessTestCase(eglTestCtx, name, description)
    {
    }

    TestCase::IterateResult iterate(void)
    {
        TestLog &log = m_testCtx.getLog();

        log << tcu::TestLog::Message
            << "Check the reset notification strategy returned by glGetIntegerv() equals GL_LOSE_CONTEXT_ON_RESET\n\n"
            << tcu::TestLog::EndMessage;

        const EGLint attribList[] = {EGL_CONTEXT_CLIENT_VERSION,
                                     3,
                                     EGL_CONTEXT_MINOR_VERSION_KHR,
                                     0,
                                     EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT,
                                     EGL_TRUE,
                                     EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT,
                                     EGL_LOSE_CONTEXT_ON_RESET,
                                     EGL_NONE};

        checkRequiredEGLExtensions(attribList);

        RenderingContext context(m_eglTestCtx, attribList, m_eglConfig, m_eglDisplay, EGL_NO_CONTEXT);
        context.makeCurrent(m_eglSurface);

        glw::Functions gl;
        {
            const glu::ApiType apiType(3, 0, glu::PROFILE_ES);
            context.initGLFunctions(&gl, apiType);
            checkRequiredGLSupport(gl, apiType);
        }
        glw::GLint reset = 0;
        gl.getIntegerv(GL_RESET_NOTIFICATION_STRATEGY, &reset);
        GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");

        if (reset != GL_LOSE_CONTEXT_ON_RESET)
        {
            log << tcu::TestLog::Message << "Test failed! glGetIntegerv() returned wrong value. [" << reset
                << ", expected " << glu::getErrorStr(GL_LOSE_CONTEXT_ON_RESET) << "]" << tcu::TestLog::EndMessage;

            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
            return STOP;
        }

        log << tcu::TestLog::Message << "Check the graphics reset status returned by glGetGraphicsResetStatus() "
            << "equals GL_NO_ERROR\n"
            << tcu::TestLog::EndMessage;

        GLU_CHECK_GLW_CALL(gl, getGraphicsResetStatus());

        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        return STOP;
    }
};

de::SharedPtr<ContextReset> contextResetFactory(const RobustnessTestCase::Params params, glw::Functions &gl,
                                                tcu::TestLog &log)
{
    if (params.getContextResetType() == CONTEXTRESETTYPE_FIXED_FUNC_OOB)
        return de::SharedPtr<ContextReset>(new FixedFunctionOOB(gl, log, params.getFixedFunctionType()));

    if (params.getContextResetType() == CONTEXTRESETTYPE_SHADER_OOB)
        return de::SharedPtr<ContextReset>(
            new ShadersOOB(gl, log, params.getShaderType(), params.getResourceType(), params.getReadWriteType()));
    else
    {
        DE_FATAL("Unknown context reset type");
        return de::SharedPtr<ContextReset>(DE_NULL);
    }
}

class ContextResetCase : public RobustnessTestCase
{

public:
    ContextResetCase(EglTestContext &eglTestCtx, const char *name, const char *description, Params params);
    virtual ~ContextResetCase(void)
    {
    }

    virtual void provokeReset(de::SharedPtr<ContextReset> &contextReset) = 0;
    virtual void waitForReset(de::SharedPtr<ContextReset> &contextReset) = 0;
    virtual void passAndLog(de::SharedPtr<ContextReset> &contextReset)   = 0;

    TestCase::IterateResult iterate(void);
    void execute(glw::Functions &gl);

private:
    ContextResetCase(const ContextResetCase &);
    ContextResetCase &operator=(const ContextResetCase &);
};

ContextResetCase::ContextResetCase(EglTestContext &eglTestCtx, const char *name, const char *description, Params params)
    : RobustnessTestCase(eglTestCtx, name, description, params)
{
}

TestCase::IterateResult ContextResetCase::iterate(void)
{
    glw::Functions gl;

    const EGLint attribList[] = {EGL_CONTEXT_CLIENT_VERSION,
                                 3,
                                 EGL_CONTEXT_MINOR_VERSION_KHR,
                                 0,
                                 EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT,
                                 (m_params.getRobustAccessType() == ROBUSTACCESS_TRUE) ? EGL_TRUE : EGL_FALSE,
                                 EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT,
                                 EGL_LOSE_CONTEXT_ON_RESET,
                                 EGL_NONE};

    checkRequiredEGLExtensions(attribList);

    RenderingContext context(m_eglTestCtx, attribList, m_eglConfig, m_eglDisplay, EGL_NO_CONTEXT);
    context.makeCurrent(m_eglSurface);

    {
        const glu::ApiType apiType = paramsToApiType(m_params);
        context.initGLFunctions(&gl, apiType);
        checkGLSupportForParams(gl, m_params);
    }

    execute(gl);

    return STOP;
}

void ContextResetCase::execute(glw::Functions &gl)
{
    de::SharedPtr<ContextReset> contextReset = contextResetFactory(m_params, gl, m_testCtx.getLog());
    glw::GLboolean isContextRobust           = GL_FALSE;

    GLU_CHECK_GLW_CALL(gl, getBooleanv(GL_CONTEXT_ROBUST_ACCESS_EXT, &isContextRobust));
    provokeReset(contextReset);

    if (m_params.getContextResetType() == CONTEXTRESETTYPE_SHADER_OOB ||
        m_params.getContextResetType() == CONTEXTRESETTYPE_FIXED_FUNC_OOB)
    {
        try
        {
            waitForReset(contextReset);
            m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Context was NOT lost. Test skipped");
        }
        catch (const glu::Error &error)
        {
            if (error.getError() == GL_CONTEXT_LOST)
            {
                if (isContextRobust)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL,
                                            "No context reset should of occurred GL_CONTEXT_ROBUST_ACCESS == TRUE");
                else
                    passAndLog(contextReset);
            }
            else if (isContextRobust)
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unknown error.");
            else
            {
                m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING,
                                        "Warning: glGetError() returned wrong value. Expected GL_CONTEXT_LOST");

                m_testCtx.getLog() << tcu::TestLog::Message << "Warning: glGetError() returned wrong value ["
                                   << error.what() << ", expected " << glu::getErrorStr(GL_CONTEXT_LOST) << "]"
                                   << tcu::TestLog::EndMessage;
            }
        }
    }
    else
        DE_FATAL("Unknown context reset type");
}

class BasicResetCase : public ContextResetCase
{
public:
    BasicResetCase(EglTestContext &eglTestCtx, const char *name, const char *description, Params params)
        : ContextResetCase(eglTestCtx, name, description, params)
    {
    }

    virtual void provokeReset(de::SharedPtr<ContextReset> &contextReset)
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Check the graphics reset status returned by glGetGraphicsResetStatus() equals "
                           << "GL_GUILTY_CONTEXT_RESET after a context reset\n\n"
                           << tcu::TestLog::EndMessage;

        contextReset->setup();
        contextReset->draw();
    }

    virtual void waitForReset(de::SharedPtr<ContextReset> &contextReset)
    {
        contextReset->teardown();
        contextReset->finish();
    }

    virtual void passAndLog(de::SharedPtr<ContextReset> &contextReset)
    {
        const glw::GLint status = contextReset->getGraphicsResetStatus();

        if (status == GL_NO_ERROR)
        {
            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Test failed! glGetGraphicsResetStatus() returned wrong value ["
                               << glu::getGraphicsResetStatusStr(status) << ", expected "
                               << glu::getGraphicsResetStatusStr(GL_GUILTY_CONTEXT_RESET) << "]"
                               << tcu::TestLog::EndMessage;

            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
        }
        else
        {
            if (contextReset->getError() != GL_NO_ERROR)
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL,
                                        "Error flag not reset after calling getGraphicsResetStatus()");
            else
                m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        }
    }
};

class InvalidShareContextCase : public RobustnessTestCase
{
public:
    InvalidShareContextCase(EglTestContext &eglTestCtx, const char *name, const char *description)
        : RobustnessTestCase(eglTestCtx, name, description)
    {
    }

    TestCase::IterateResult iterate(void)
    {
        TestLog &log       = m_testCtx.getLog();
        const Library &egl = m_eglTestCtx.getLibrary();
        bool isOk          = true;

        log << tcu::TestLog::Message
            << "EGL_BAD_MATCH is generated if reset notification strategies do not match when creating shared "
               "contexts\n\n"
            << tcu::TestLog::EndMessage;

        const EGLint attribListA[] = {EGL_CONTEXT_CLIENT_VERSION,
                                      3,
                                      EGL_CONTEXT_MINOR_VERSION_KHR,
                                      0,
                                      EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT,
                                      EGL_TRUE,
                                      EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT,
                                      EGL_NO_RESET_NOTIFICATION,
                                      EGL_NONE};

        const EGLint attribListB[] = {EGL_CONTEXT_CLIENT_VERSION,
                                      3,
                                      EGL_CONTEXT_MINOR_VERSION_KHR,
                                      0,
                                      EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT,
                                      EGL_TRUE,
                                      EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT,
                                      EGL_LOSE_CONTEXT_ON_RESET,
                                      EGL_NONE};

        checkRequiredEGLExtensions(attribListA);

        log << tcu::TestLog::Message << "Create context A (share_context = EGL_NO_CONTEXT)" << tcu::TestLog::EndMessage;
        RenderingContext contextA(m_eglTestCtx, attribListA, m_eglConfig, m_eglDisplay, EGL_NO_CONTEXT);

        log << tcu::TestLog::Message << "Create context B (share_context = context A)" << tcu::TestLog::EndMessage;
        logAttribList(m_eglTestCtx, attribListB);

        EGLContext contextB = egl.createContext(m_eglDisplay, m_eglConfig, contextA.getContext(), attribListB);

        const EGLenum error = egl.getError();
        if (error != EGL_BAD_MATCH)
        {
            log << TestLog::Message << "Test failed! eglCreateContext() returned with error ["
                << eglu::getErrorStr(error) << ", expected " << eglu::getErrorStr(EGL_BAD_MATCH) << "]"
                << TestLog::EndMessage;

            isOk = false;
        }

        if (contextB != EGL_NO_CONTEXT)
            egl.destroyContext(m_eglDisplay, contextB);

        if (isOk)
            m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        else
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

        return STOP;
    }
};

class InvalidNotificationEnumCase : public RobustnessTestCase
{
public:
    InvalidNotificationEnumCase(EglTestContext &eglTestCtx, const char *name, const char *description)
        : RobustnessTestCase(eglTestCtx, name, description)
    {
    }

    TestCase::IterateResult iterate(void)
    {
        TestLog &log       = m_testCtx.getLog();
        const Library &egl = m_eglTestCtx.getLibrary();
        bool isOk          = true;

        log << tcu::TestLog::Message
            << "EGL_BAD_ATTRIBUTE is generated if EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR is used with EGL "
               "versions <= 1.4\n\n"
            << tcu::TestLog::EndMessage;

        const EGLint attribList[] = {EGL_CONTEXT_CLIENT_VERSION,
                                     3,
                                     EGL_CONTEXT_MINOR_VERSION_KHR,
                                     1,
                                     EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR,
                                     EGL_NO_RESET_NOTIFICATION,
                                     EGL_NONE};

        if (eglu::getVersion(egl, m_eglDisplay) >= eglu::Version(1, 5))
        {
            m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Test requires EGL version to be under 1.5");
            return STOP;
        }

        logAttribList(m_eglTestCtx, attribList);
        EGLContext context = egl.createContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, attribList);

        const EGLenum error = egl.getError();
        if (error != EGL_BAD_ATTRIBUTE)
        {
            log << TestLog::Message << "Test failed! eglCreateContext() returned with error ["
                << eglu::getErrorStr(error) << ", expected " << eglu::getErrorStr(EGL_BAD_ATTRIBUTE) << "]"
                << TestLog::EndMessage;

            isOk = false;
        }

        if (context != EGL_NO_CONTEXT)
            egl.destroyContext(m_eglDisplay, context);

        if (isOk)
            m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        else
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

        return STOP;
    }
};

class InvalidContextCase : public RobustnessTestCase
{
public:
    InvalidContextCase(EglTestContext &eglTestCtx, const char *name, const char *description)
        : RobustnessTestCase(eglTestCtx, name, description)
    {
    }

    TestCase::IterateResult iterate(void)
    {
        const Library &egl = m_eglTestCtx.getLibrary();
        TestLog &log       = m_testCtx.getLog();
        bool isOk          = true;

        log << tcu::TestLog::Message
            << "EGL_BAD_ATTRIBUTE is generated if EXT_create_context_robustness is NOT supported but "
               "EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT is specified\n\n"
            << tcu::TestLog::EndMessage;

        const EGLint attribList[] = {EGL_CONTEXT_CLIENT_VERSION,
                                     3,
                                     EGL_CONTEXT_MINOR_VERSION_KHR,
                                     0,
                                     EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT,
                                     EGL_LOSE_CONTEXT_ON_RESET,
                                     EGL_NONE};

        if (eglu::hasExtension(egl, m_eglDisplay, "EGL_EXT_create_context_robustness"))
        {
            m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED,
                                    "Test requires EGL_EXT_create_context_robustness to be unsupported");
            return STOP;
        }

        logAttribList(m_eglTestCtx, attribList);
        EGLContext context = egl.createContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, attribList);

        const EGLenum error = egl.getError();
        if (error != EGL_BAD_ATTRIBUTE)
        {
            log << TestLog::Message << "Test failed! eglCreateContext() returned with error ["
                << eglu::getErrorStr(error) << ", expected " << eglu::getErrorStr(EGL_BAD_ATTRIBUTE) << "]"
                << TestLog::EndMessage;

            isOk = false;
        }

        if (context != EGL_NO_CONTEXT)
            egl.destroyContext(m_eglDisplay, context);

        if (isOk)
            m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        else
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

        return STOP;
    }
};

} // namespace

// Note: Tests limited to openGLES 3.1 contexts only
TestCaseGroup *createRobustnessTests(EglTestContext &eglTestCtx)
{
    de::MovePtr<TestCaseGroup> group(new TestCaseGroup(eglTestCtx, "robustness", "KHR_robustness tests"));

    tcu::TestCaseGroup *const contextCreationTestGroup =
        new TestCaseGroup(eglTestCtx, "create_context", "Test valid context_creation attributes");
    tcu::TestCaseGroup *const contextResetTestGroup =
        new TestCaseGroup(eglTestCtx, "reset_context", "Test context resets scenarios");
    tcu::TestCaseGroup *const negativeContextTestGroup =
        new TestCaseGroup(eglTestCtx, "negative_context", "Test invalid context creation attributes");

    tcu::TestCaseGroup *const shadersTestGroup =
        new TestCaseGroup(eglTestCtx, "shaders", "Shader specific context reset tests");
    tcu::TestCaseGroup *const fixedFunctionTestGroup = new TestCaseGroup(
        eglTestCtx, "fixed_function_pipeline", "Fixed function pipeline context reset tests with robust context");
    tcu::TestCaseGroup *const fixedFunctionNonRobustTestGroup =
        new TestCaseGroup(eglTestCtx, "fixed_function_pipeline_non_robust",
                          "Fixed function pipeline context reset tests with non-robust context");

    tcu::TestCaseGroup *const outOfBoundsTestGroup =
        new TestCaseGroup(eglTestCtx, "out_of_bounds", "Out of bounds access scenarios with robust context");

    tcu::TestCaseGroup *const outOfBoundsNonRobustTestGroup = new TestCaseGroup(
        eglTestCtx, "out_of_bounds_non_robust", "Out of bounds access scenarios with non-robust context");

    const string resetScenarioDescription = "query error states and reset notifications";

    // out-of-bounds test cases
    {
        // robust context
        tcu::TestCaseGroup *const uboReadArrayResetTestGroup =
            new TestCaseGroup(eglTestCtx, "uniform_block", "Uniform Block Accesses");
        tcu::TestCaseGroup *const uboWriteArrayResetTestGroup =
            new TestCaseGroup(eglTestCtx, "uniform_block", "Uniform Block Accesses");
        tcu::TestCaseGroup *const ssboWriteArrayResetTestGroup =
            new TestCaseGroup(eglTestCtx, "shader_storage_block", "Shader Storage Block accesses");
        tcu::TestCaseGroup *const ssboReadArrayResetTestGroup =
            new TestCaseGroup(eglTestCtx, "shader_storage_block", "Shader Storage Block accesses");
        tcu::TestCaseGroup *const localWriteArrayResetTestGroup =
            new TestCaseGroup(eglTestCtx, "local_array", "Local array accesses");
        tcu::TestCaseGroup *const localReadArrayResetTestGroup =
            new TestCaseGroup(eglTestCtx, "local_array", "Local array accesses");

        // non-robust context (internal use only)
        tcu::TestCaseGroup *const uboReadArrayResetNonRobustTestGroup =
            new TestCaseGroup(eglTestCtx, "uniform_block", "Uniform Block Accesses");
        tcu::TestCaseGroup *const uboWriteArrayResetNonRobustTestGroup =
            new TestCaseGroup(eglTestCtx, "uniform_block", "Uniform Block Accesses");
        tcu::TestCaseGroup *const ssboWriteArrayResetNonRobustTestGroup =
            new TestCaseGroup(eglTestCtx, "shader_storage_block", "Shader Storage Block accesses");
        tcu::TestCaseGroup *const ssboReadArrayResetNonRobustTestGroup =
            new TestCaseGroup(eglTestCtx, "shader_storage_block", "Shader Storage Block accesses");
        tcu::TestCaseGroup *const localWriteArrayResetNonRobustTestGroup =
            new TestCaseGroup(eglTestCtx, "local_array", "Local array accesses");
        tcu::TestCaseGroup *const localReadArrayResetNonRobustTestGroup =
            new TestCaseGroup(eglTestCtx, "local_array", "Local array accesses");

        static const RobustnessTestCase::Params s_outOfBoundReadCases[] = {
            // ubo read only
            RobustnessTestCase::Params("vertex", "Provoke a context reset in vertex shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT, RESOURCETYPE_UBO,
                                       READWRITETYPE_READ),
            RobustnessTestCase::Params("fragment", "Provoke a context reset in fragment shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_FRAG, RESOURCETYPE_UBO,
                                       READWRITETYPE_READ),
            RobustnessTestCase::Params(
                "vertex_and_fragment", "Provoke a context reset in vertex and fragment shader and ", ROBUSTACCESS_TRUE,
                CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT_AND_FRAG, RESOURCETYPE_UBO, READWRITETYPE_READ),
            RobustnessTestCase::Params("compute", "Provoke a context reset in compute shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_COMPUTE, RESOURCETYPE_UBO,
                                       READWRITETYPE_READ),

            // ssbo read only
            RobustnessTestCase::Params("vertex", "Provoke a context reset in vertex shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT, RESOURCETYPE_SSBO,
                                       READWRITETYPE_READ),
            RobustnessTestCase::Params("fragment", "Provoke a context reset in fragment shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_FRAG, RESOURCETYPE_SSBO,
                                       READWRITETYPE_READ),
            RobustnessTestCase::Params(
                "vertex_and_fragment", "Provoke a context reset in vertex and fragment shader and ", ROBUSTACCESS_TRUE,
                CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT_AND_FRAG, RESOURCETYPE_SSBO, READWRITETYPE_READ),
            RobustnessTestCase::Params("compute", "Provoke a context reset in compute shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_COMPUTE, RESOURCETYPE_SSBO,
                                       READWRITETYPE_READ),

            // local array read only
            RobustnessTestCase::Params("vertex", "Provoke a context reset in vertex shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT, RESOURCETYPE_LOCAL_ARRAY,
                                       READWRITETYPE_READ),
            RobustnessTestCase::Params("fragment", "Provoke a context reset in fragment shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_FRAG, RESOURCETYPE_LOCAL_ARRAY,
                                       READWRITETYPE_READ),
            RobustnessTestCase::Params(
                "vertex_and_fragment", "Provoke a context reset in vertex and fragment shader and ", ROBUSTACCESS_TRUE,
                CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT_AND_FRAG, RESOURCETYPE_LOCAL_ARRAY, READWRITETYPE_READ),
            RobustnessTestCase::Params("compute", "Provoke a context reset in compute shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_COMPUTE, RESOURCETYPE_LOCAL_ARRAY,
                                       READWRITETYPE_READ),

            // ubo read only (non-robust)
            RobustnessTestCase::Params("vertex", "Provoke a context reset in vertex shader and ", ROBUSTACCESS_FALSE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT, RESOURCETYPE_UBO,
                                       READWRITETYPE_READ),
            RobustnessTestCase::Params("fragment", "Provoke a context reset in fragment shader and ",
                                       ROBUSTACCESS_FALSE, CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_FRAG,
                                       RESOURCETYPE_UBO, READWRITETYPE_READ),
            RobustnessTestCase::Params(
                "vertex_and_fragment", "Provoke a context reset in vertex and fragment shader and ", ROBUSTACCESS_FALSE,
                CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT_AND_FRAG, RESOURCETYPE_UBO, READWRITETYPE_READ),
            RobustnessTestCase::Params("compute", "Provoke a context reset in compute shader and ", ROBUSTACCESS_FALSE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_COMPUTE, RESOURCETYPE_UBO,
                                       READWRITETYPE_READ),

            // ssbo read only (non-robust)
            RobustnessTestCase::Params("vertex", "Provoke a context reset in vertex shader and ", ROBUSTACCESS_FALSE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT, RESOURCETYPE_SSBO,
                                       READWRITETYPE_READ),
            RobustnessTestCase::Params("fragment", "Provoke a context reset in fragment shader and ",
                                       ROBUSTACCESS_FALSE, CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_FRAG,
                                       RESOURCETYPE_SSBO, READWRITETYPE_READ),
            RobustnessTestCase::Params(
                "vertex_and_fragment", "Provoke a context reset in vertex and fragment shader and ", ROBUSTACCESS_FALSE,
                CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT_AND_FRAG, RESOURCETYPE_SSBO, READWRITETYPE_READ),
            RobustnessTestCase::Params("compute", "Provoke a context reset in compute shader and ", ROBUSTACCESS_FALSE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_COMPUTE, RESOURCETYPE_SSBO,
                                       READWRITETYPE_READ),

            // local array read only (non-robust)
            RobustnessTestCase::Params("vertex", "Provoke a context reset in vertex shader and ", ROBUSTACCESS_FALSE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT, RESOURCETYPE_LOCAL_ARRAY,
                                       READWRITETYPE_READ),
            RobustnessTestCase::Params("fragment", "Provoke a context reset in fragment shader and ",
                                       ROBUSTACCESS_FALSE, CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_FRAG,
                                       RESOURCETYPE_LOCAL_ARRAY, READWRITETYPE_READ),
            RobustnessTestCase::Params(
                "vertex_and_fragment", "Provoke a context reset in vertex and fragment shader and ", ROBUSTACCESS_FALSE,
                CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT_AND_FRAG, RESOURCETYPE_LOCAL_ARRAY, READWRITETYPE_READ),
            RobustnessTestCase::Params("compute", "Provoke a context reset in compute shader and ", ROBUSTACCESS_FALSE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_COMPUTE, RESOURCETYPE_LOCAL_ARRAY,
                                       READWRITETYPE_READ),
        };

        for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(s_outOfBoundReadCases); ++testNdx)
        {
            const RobustnessTestCase::Params &test = s_outOfBoundReadCases[testNdx];

            if (test.getResourceType() == RESOURCETYPE_UBO && test.getRobustAccessType() == ROBUSTACCESS_TRUE)
                uboReadArrayResetTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(),
                                       (test.getDescription() + resetScenarioDescription).c_str(), test));

            if (test.getResourceType() == RESOURCETYPE_UBO && test.getRobustAccessType() == ROBUSTACCESS_FALSE)
                uboReadArrayResetNonRobustTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(),
                                       (test.getDescription() + resetScenarioDescription).c_str(), test));

            if (test.getResourceType() == RESOURCETYPE_SSBO && test.getRobustAccessType() == ROBUSTACCESS_TRUE)
                ssboReadArrayResetTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(),
                                       (test.getDescription() + resetScenarioDescription).c_str(), test));

            if (test.getResourceType() == RESOURCETYPE_SSBO && test.getRobustAccessType() == ROBUSTACCESS_FALSE)
                ssboReadArrayResetNonRobustTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(),
                                       (test.getDescription() + resetScenarioDescription).c_str(), test));

            if (test.getResourceType() == RESOURCETYPE_LOCAL_ARRAY && test.getRobustAccessType() == ROBUSTACCESS_TRUE)
                localReadArrayResetTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(),
                                       (test.getDescription() + resetScenarioDescription).c_str(), test));

            if (test.getResourceType() == RESOURCETYPE_LOCAL_ARRAY && test.getRobustAccessType() == ROBUSTACCESS_FALSE)
                localReadArrayResetNonRobustTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(),
                                       (test.getDescription() + resetScenarioDescription).c_str(), test));
        }

        static const RobustnessTestCase::Params s_outOfBoundWriteCases[] = {
            // ubo write only
            RobustnessTestCase::Params("vertex", "Provoke a context reset in vertex shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT, RESOURCETYPE_UBO,
                                       READWRITETYPE_WRITE),
            RobustnessTestCase::Params("fragment", "Provoke a context reset in fragment shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_FRAG, RESOURCETYPE_UBO,
                                       READWRITETYPE_WRITE),
            RobustnessTestCase::Params(
                "vertex_and_fragment", "Provoke a context reset in vertex and fragment shader and ", ROBUSTACCESS_TRUE,
                CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT_AND_FRAG, RESOURCETYPE_UBO, READWRITETYPE_WRITE),
            RobustnessTestCase::Params("compute", "Provoke a context reset in compute shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_COMPUTE, RESOURCETYPE_UBO,
                                       READWRITETYPE_WRITE),

            // ssbo write only
            RobustnessTestCase::Params("vertex", "Provoke a context reset in vertex shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT, RESOURCETYPE_SSBO,
                                       READWRITETYPE_WRITE),
            RobustnessTestCase::Params("fragment", "Provoke a context reset in fragment shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_FRAG, RESOURCETYPE_SSBO,
                                       READWRITETYPE_WRITE),
            RobustnessTestCase::Params(
                "vertex_and_fragment", "Provoke a context reset in vertex and fragment shader and ", ROBUSTACCESS_TRUE,
                CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT_AND_FRAG, RESOURCETYPE_SSBO, READWRITETYPE_WRITE),
            RobustnessTestCase::Params("compute", "Provoke a context reset in compute shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_COMPUTE, RESOURCETYPE_SSBO,
                                       READWRITETYPE_WRITE),

            // local array write only
            RobustnessTestCase::Params("vertex", "Provoke a context reset in vertex shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT, RESOURCETYPE_LOCAL_ARRAY,
                                       READWRITETYPE_WRITE),
            RobustnessTestCase::Params("fragment", "Provoke a context reset in fragment shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_FRAG, RESOURCETYPE_LOCAL_ARRAY,
                                       READWRITETYPE_WRITE),
            RobustnessTestCase::Params(
                "vertex_and_fragment", "Provoke a context reset in vertex and fragment shader and ", ROBUSTACCESS_TRUE,
                CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT_AND_FRAG, RESOURCETYPE_LOCAL_ARRAY, READWRITETYPE_WRITE),
            RobustnessTestCase::Params("compute", "Provoke a context reset in compute shader and ", ROBUSTACCESS_TRUE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_COMPUTE, RESOURCETYPE_LOCAL_ARRAY,
                                       READWRITETYPE_WRITE),

            // ubo write only (non-robust)
            RobustnessTestCase::Params("vertex", "Provoke a context reset in vertex shader and ", ROBUSTACCESS_FALSE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT, RESOURCETYPE_UBO,
                                       READWRITETYPE_WRITE),
            RobustnessTestCase::Params("fragment", "Provoke a context reset in fragment shader and ",
                                       ROBUSTACCESS_FALSE, CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_FRAG,
                                       RESOURCETYPE_UBO, READWRITETYPE_WRITE),
            RobustnessTestCase::Params(
                "vertex_and_fragment", "Provoke a context reset in vertex and fragment shader and ", ROBUSTACCESS_FALSE,
                CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT_AND_FRAG, RESOURCETYPE_UBO, READWRITETYPE_WRITE),
            RobustnessTestCase::Params("compute", "Provoke a context reset in compute shader and ", ROBUSTACCESS_FALSE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_COMPUTE, RESOURCETYPE_UBO,
                                       READWRITETYPE_WRITE),

            // ssbo write only (non-robust)
            RobustnessTestCase::Params("vertex", "Provoke a context reset in vertex shader and ", ROBUSTACCESS_FALSE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT, RESOURCETYPE_SSBO,
                                       READWRITETYPE_WRITE),
            RobustnessTestCase::Params("fragment", "Provoke a context reset in fragment shader and ",
                                       ROBUSTACCESS_FALSE, CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_FRAG,
                                       RESOURCETYPE_SSBO, READWRITETYPE_WRITE),
            RobustnessTestCase::Params(
                "vertex_and_fragment", "Provoke a context reset in vertex and fragment shader and ", ROBUSTACCESS_FALSE,
                CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT_AND_FRAG, RESOURCETYPE_SSBO, READWRITETYPE_WRITE),
            RobustnessTestCase::Params("compute", "Provoke a context reset in compute shader and ", ROBUSTACCESS_FALSE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_COMPUTE, RESOURCETYPE_SSBO,
                                       READWRITETYPE_WRITE),

            // local array write only (non-robust)
            RobustnessTestCase::Params("vertex", "Provoke a context reset in vertex shader and ", ROBUSTACCESS_FALSE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT, RESOURCETYPE_LOCAL_ARRAY,
                                       READWRITETYPE_WRITE),
            RobustnessTestCase::Params("fragment", "Provoke a context reset in fragment shader and ",
                                       ROBUSTACCESS_FALSE, CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_FRAG,
                                       RESOURCETYPE_LOCAL_ARRAY, READWRITETYPE_WRITE),
            RobustnessTestCase::Params(
                "vertex_and_fragment", "Provoke a context reset in vertex and fragment shader and ", ROBUSTACCESS_FALSE,
                CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_VERT_AND_FRAG, RESOURCETYPE_LOCAL_ARRAY, READWRITETYPE_WRITE),
            RobustnessTestCase::Params("compute", "Provoke a context reset in compute shader and ", ROBUSTACCESS_FALSE,
                                       CONTEXTRESETTYPE_SHADER_OOB, SHADERTYPE_COMPUTE, RESOURCETYPE_LOCAL_ARRAY,
                                       READWRITETYPE_WRITE),
        };

        for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(s_outOfBoundWriteCases); ++testNdx)
        {
            const RobustnessTestCase::Params &test = s_outOfBoundWriteCases[testNdx];

            if (test.getResourceType() == RESOURCETYPE_UBO && test.getRobustAccessType() == ROBUSTACCESS_TRUE)
                uboWriteArrayResetTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(),
                                       (test.getDescription() + resetScenarioDescription).c_str(), test));

            if (test.getResourceType() == RESOURCETYPE_UBO && test.getRobustAccessType() == ROBUSTACCESS_FALSE)
                uboWriteArrayResetNonRobustTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(),
                                       (test.getDescription() + resetScenarioDescription).c_str(), test));

            if (test.getResourceType() == RESOURCETYPE_SSBO && test.getRobustAccessType() == ROBUSTACCESS_TRUE)
                ssboWriteArrayResetTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(),
                                       (test.getDescription() + resetScenarioDescription).c_str(), test));

            if (test.getResourceType() == RESOURCETYPE_SSBO && test.getRobustAccessType() == ROBUSTACCESS_FALSE)
                ssboWriteArrayResetNonRobustTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(),
                                       (test.getDescription() + resetScenarioDescription).c_str(), test));

            if (test.getResourceType() == RESOURCETYPE_LOCAL_ARRAY && test.getRobustAccessType() == ROBUSTACCESS_TRUE)
                localWriteArrayResetTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(),
                                       (test.getDescription() + resetScenarioDescription).c_str(), test));

            if (test.getResourceType() == RESOURCETYPE_LOCAL_ARRAY && test.getRobustAccessType() == ROBUSTACCESS_FALSE)
                localWriteArrayResetNonRobustTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(),
                                       (test.getDescription() + resetScenarioDescription).c_str(), test));
        }

        // robust Context
        tcu::TestCaseGroup *const outOfBoundsResetReadAccessTestGroup =
            new TestCaseGroup(eglTestCtx, "reads", "Out of bounds read accesses");
        tcu::TestCaseGroup *const outOfBoundsResetWriteAccessTestGroup =
            new TestCaseGroup(eglTestCtx, "writes", "Out of bounds write accesses");

        outOfBoundsResetReadAccessTestGroup->addChild(uboReadArrayResetTestGroup);
        outOfBoundsResetReadAccessTestGroup->addChild(ssboReadArrayResetTestGroup);
        outOfBoundsResetReadAccessTestGroup->addChild(localReadArrayResetTestGroup);

        outOfBoundsResetWriteAccessTestGroup->addChild(uboWriteArrayResetTestGroup);
        outOfBoundsResetWriteAccessTestGroup->addChild(ssboWriteArrayResetTestGroup);
        outOfBoundsResetWriteAccessTestGroup->addChild(localWriteArrayResetTestGroup);

        tcu::TestCaseGroup *const outOfBoundsResetTestGroup = new TestCaseGroup(
            eglTestCtx, "reset_status", "Tests that query the reset status after a context reset has occurred");

        outOfBoundsResetTestGroup->addChild(outOfBoundsResetReadAccessTestGroup);
        outOfBoundsResetTestGroup->addChild(outOfBoundsResetWriteAccessTestGroup);

        outOfBoundsTestGroup->addChild(outOfBoundsResetTestGroup);

        // non-robust Context (internal use only)
        tcu::TestCaseGroup *const outOfBoundsResetReadAccessNonRobustTestGroup =
            new TestCaseGroup(eglTestCtx, "reads", "Out of bounds read accesses");
        tcu::TestCaseGroup *const outOfBoundsResetWriteAccessNonRobustTestGroup =
            new TestCaseGroup(eglTestCtx, "writes", "Out of bounds write accesses");

        outOfBoundsResetReadAccessNonRobustTestGroup->addChild(uboReadArrayResetNonRobustTestGroup);
        outOfBoundsResetReadAccessNonRobustTestGroup->addChild(ssboReadArrayResetNonRobustTestGroup);
        outOfBoundsResetReadAccessNonRobustTestGroup->addChild(localReadArrayResetNonRobustTestGroup);

        outOfBoundsResetWriteAccessNonRobustTestGroup->addChild(uboWriteArrayResetNonRobustTestGroup);
        outOfBoundsResetWriteAccessNonRobustTestGroup->addChild(ssboWriteArrayResetNonRobustTestGroup);
        outOfBoundsResetWriteAccessNonRobustTestGroup->addChild(localWriteArrayResetNonRobustTestGroup);

        tcu::TestCaseGroup *const outOfBoundsResetNonRobustTestGroup = new TestCaseGroup(
            eglTestCtx, "reset_status", "Tests that query the reset status after a context reset has occurred");

        outOfBoundsResetNonRobustTestGroup->addChild(outOfBoundsResetReadAccessNonRobustTestGroup);
        outOfBoundsResetNonRobustTestGroup->addChild(outOfBoundsResetWriteAccessNonRobustTestGroup);

        outOfBoundsNonRobustTestGroup->addChild(outOfBoundsResetNonRobustTestGroup);
    }

    // fixed function test cases
    {
        // robust context
        tcu::TestCaseGroup *const fixedFunctionResetStatusTestGroup = new TestCaseGroup(
            eglTestCtx, "reset_status", "Tests that query the reset status after a context reset has occurred");

        // non-robust context (internal use only)
        tcu::TestCaseGroup *const fixedFunctionResetStatusNonRobustTestGroup = new TestCaseGroup(
            eglTestCtx, "reset_status", "Tests that query the reset status after a context reset has occurred");

        static const RobustnessTestCase::Params s_fixedFunctionPipelineCases[] = {
            RobustnessTestCase::Params("index_buffer_out_of_bounds",
                                       "Provoke context reset and query error states and reset notifications",
                                       ROBUSTACCESS_TRUE, CONTEXTRESETTYPE_FIXED_FUNC_OOB, FIXEDFUNCTIONTYPE_INDICES),
            RobustnessTestCase::Params("vertex_buffer_out_of_bounds",
                                       "Provoke context reset and query error states and reset notifications",
                                       ROBUSTACCESS_TRUE, CONTEXTRESETTYPE_FIXED_FUNC_OOB, FIXEDFUNCTIONTYPE_VERTICES),

            RobustnessTestCase::Params("index_buffer_out_of_bounds",
                                       "Provoke context reset and query error states and reset notifications",
                                       ROBUSTACCESS_FALSE, CONTEXTRESETTYPE_FIXED_FUNC_OOB, FIXEDFUNCTIONTYPE_INDICES),
            RobustnessTestCase::Params("vertex_buffer_out_of_bounds",
                                       "Provoke context reset and query error states and reset notifications",
                                       ROBUSTACCESS_FALSE, CONTEXTRESETTYPE_FIXED_FUNC_OOB, FIXEDFUNCTIONTYPE_VERTICES),
        };

        for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(s_fixedFunctionPipelineCases); ++testNdx)
        {
            const RobustnessTestCase::Params &test = s_fixedFunctionPipelineCases[testNdx];
            if (test.getRobustAccessType() == ROBUSTACCESS_TRUE)
                fixedFunctionResetStatusTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(), test.getDescription().c_str(), test));
            else
                fixedFunctionResetStatusNonRobustTestGroup->addChild(
                    new BasicResetCase(eglTestCtx, test.getName().c_str(), test.getDescription().c_str(), test));
        }

        fixedFunctionTestGroup->addChild(fixedFunctionResetStatusTestGroup);
        fixedFunctionNonRobustTestGroup->addChild(fixedFunctionResetStatusNonRobustTestGroup);
    }

    // context creation query cases
    {
        contextCreationTestGroup->addChild(new QueryRobustAccessCase(
            eglTestCtx, "query_robust_access", "Query robust access after successfully creating a robust context"));
        contextCreationTestGroup->addChild(
            new NoResetNotificationCase(eglTestCtx, "no_reset_notification",
                                        "Query reset notification strategy after specifying GL_NO_RESET_NOTIFICATION"));
        contextCreationTestGroup->addChild(
            new LoseContextOnResetCase(eglTestCtx, "lose_context_on_reset",
                                       "Query reset notification strategy after specifying GL_LOSE_CONTEXT_ON_RESET"));
    }

    // invalid context creation cases
    {
        negativeContextTestGroup->addChild(
            new InvalidContextCase(eglTestCtx, "invalid_robust_context_creation",
                                   "Create a non-robust context but specify a reset notification strategy"));
        negativeContextTestGroup->addChild(
            new InvalidShareContextCase(eglTestCtx, "invalid_robust_shared_context_creation",
                                        "Create a context share group with conflicting reset notification strategies"));
        negativeContextTestGroup->addChild(new InvalidNotificationEnumCase(
            eglTestCtx, "invalid_notification_strategy_enum",
            "Create a robust context using EGL 1.5 only enum with EGL versions <= 1.4"));
    }

    shadersTestGroup->addChild(outOfBoundsTestGroup);
    shadersTestGroup->addChild(outOfBoundsNonRobustTestGroup);

    contextResetTestGroup->addChild(shadersTestGroup);
    contextResetTestGroup->addChild(fixedFunctionTestGroup);
    contextResetTestGroup->addChild(fixedFunctionNonRobustTestGroup);

    group->addChild(contextCreationTestGroup);
    group->addChild(contextResetTestGroup);
    group->addChild(negativeContextTestGroup);

    return group.release();
}

} // namespace egl
} // namespace deqp
