// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/test_helper.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <string>

#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_tokenizer.h"
#include "gpu/command_buffer/service/buffer_manager.h"
#include "gpu/command_buffer/service/error_state_mock.h"
#include "gpu/command_buffer/service/feature_info.h"
#include "gpu/command_buffer/service/gl_utils.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "gpu/command_buffer/service/mocks.h"
#include "gpu/command_buffer/service/program_manager.h"
#include "gpu/command_buffer/service/texture_manager.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gl/gl_mock.h"
#include "ui/gl/gl_version_info.h"

using ::testing::_;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::MatcherCast;
using ::testing::Pointee;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SetArrayArgument;
using ::testing::SetArgumentPointee;
using ::testing::SetArgPointee;
using ::testing::StrEq;
using ::testing::StrictMock;

namespace gpu {
namespace gles2 {

namespace {

template<typename T>
T ConstructShaderVariable(
    GLenum type, GLint array_size, GLenum precision,
    bool static_use, const std::string& name) {
  T var;
  var.type = type;
  var.arraySize = array_size;
  var.precision = precision;
  var.staticUse = static_use;
  var.name = name;
  var.mappedName = name;  // No name hashing.
  return var;
}

}  // namespace anonymous

// GCC requires these declarations, but MSVC requires they not be present
#ifndef COMPILER_MSVC
const GLuint TestHelper::kServiceBlackTexture2dId;
const GLuint TestHelper::kServiceDefaultTexture2dId;
const GLuint TestHelper::kServiceBlackTexture3dId;
const GLuint TestHelper::kServiceDefaultTexture3dId;
const GLuint TestHelper::kServiceBlackTexture2dArrayId;
const GLuint TestHelper::kServiceDefaultTexture2dArrayId;
const GLuint TestHelper::kServiceBlackTextureCubemapId;
const GLuint TestHelper::kServiceDefaultTextureCubemapId;
const GLuint TestHelper::kServiceBlackExternalTextureId;
const GLuint TestHelper::kServiceDefaultExternalTextureId;
const GLuint TestHelper::kServiceBlackRectangleTextureId;
const GLuint TestHelper::kServiceDefaultRectangleTextureId;

const GLint TestHelper::kMaxSamples;
const GLint TestHelper::kMaxRenderbufferSize;
const GLint TestHelper::kMaxTextureSize;
const GLint TestHelper::kMaxCubeMapTextureSize;
const GLint TestHelper::kMaxRectangleTextureSize;
const GLint TestHelper::kMax3DTextureSize;
const GLint TestHelper::kMaxArrayTextureLayers;
const GLint TestHelper::kNumVertexAttribs;
const GLint TestHelper::kNumTextureUnits;
const GLint TestHelper::kMaxTextureImageUnits;
const GLint TestHelper::kMaxVertexTextureImageUnits;
const GLint TestHelper::kMaxFragmentUniformVectors;
const GLint TestHelper::kMaxFragmentUniformComponents;
const GLint TestHelper::kMaxVaryingVectors;
const GLint TestHelper::kMaxVaryingFloats;
const GLint TestHelper::kMaxVertexUniformVectors;
const GLint TestHelper::kMaxVertexUniformComponents;
const GLint TestHelper::kMaxVertexOutputComponents;
const GLint TestHelper::kMaxFragmentInputComponents;
const GLint TestHelper::kMaxProgramTexelOffset;
const GLint TestHelper::kMinProgramTexelOffset;
const GLint TestHelper::kMaxTransformFeedbackSeparateAttribs;
const GLint TestHelper::kMaxUniformBufferBindings;
const GLint TestHelper::kUniformBufferOffsetAlignment;
#endif

std::vector<std::string> TestHelper::split_extensions_;

void TestHelper::SetupTextureInitializationExpectations(
    ::gl::MockGLInterface* gl,
    GLenum target,
    bool use_default_textures) {
  InSequence sequence;

  bool needs_initialization = (target != GL_TEXTURE_EXTERNAL_OES);
  bool needs_faces = (target == GL_TEXTURE_CUBE_MAP);
  bool is_3d_or_2d_array_target = (target == GL_TEXTURE_3D ||
      target == GL_TEXTURE_2D_ARRAY);

  static GLuint texture_2d_ids[] = {
    kServiceBlackTexture2dId,
    kServiceDefaultTexture2dId };
  static GLuint texture_3d_ids[] = {
    kServiceBlackTexture3dId,
    kServiceDefaultTexture3dId };
  static GLuint texture_2d_array_ids[] = {
    kServiceBlackTexture2dArrayId,
    kServiceDefaultTexture2dArrayId };
  static GLuint texture_cube_map_ids[] = {
    kServiceBlackTextureCubemapId,
    kServiceDefaultTextureCubemapId };
  static GLuint texture_external_oes_ids[] = {
    kServiceBlackExternalTextureId,
    kServiceDefaultExternalTextureId };
  static GLuint texture_rectangle_arb_ids[] = {
    kServiceBlackRectangleTextureId,
    kServiceDefaultRectangleTextureId };

  const GLuint* texture_ids = NULL;
  switch (target) {
    case GL_TEXTURE_2D:
      texture_ids = &texture_2d_ids[0];
      break;
    case GL_TEXTURE_3D:
      texture_ids = &texture_3d_ids[0];
      break;
    case GL_TEXTURE_2D_ARRAY:
      texture_ids = &texture_2d_array_ids[0];
      break;
    case GL_TEXTURE_CUBE_MAP:
      texture_ids = &texture_cube_map_ids[0];
      break;
    case GL_TEXTURE_EXTERNAL_OES:
      texture_ids = &texture_external_oes_ids[0];
      break;
    case GL_TEXTURE_RECTANGLE_ARB:
      texture_ids = &texture_rectangle_arb_ids[0];
      break;
    default:
      NOTREACHED();
  }

  int array_size = use_default_textures ? 2 : 1;

  EXPECT_CALL(*gl, GenTextures(array_size, _))
      .WillOnce(SetArrayArgument<1>(texture_ids,
                                    texture_ids + array_size))
          .RetiresOnSaturation();
  for (int ii = 0; ii < array_size; ++ii) {
    EXPECT_CALL(*gl, BindTexture(target, texture_ids[ii]))
        .Times(1)
        .RetiresOnSaturation();
    if (needs_initialization) {
      if (needs_faces) {
        static GLenum faces[] = {
          GL_TEXTURE_CUBE_MAP_POSITIVE_X,
          GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
          GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
          GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
          GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
          GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
        };
        for (size_t ii = 0; ii < arraysize(faces); ++ii) {
          EXPECT_CALL(*gl, TexImage2D(faces[ii], 0, GL_RGBA, 1, 1, 0, GL_RGBA,
                                      GL_UNSIGNED_BYTE, _))
              .Times(1)
              .RetiresOnSaturation();
        }
      } else {
        if (is_3d_or_2d_array_target) {
          EXPECT_CALL(*gl, TexImage3D(target, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA,
                                      GL_UNSIGNED_BYTE, _))
              .Times(1)
              .RetiresOnSaturation();
        } else {
          EXPECT_CALL(*gl, TexImage2D(target, 0, GL_RGBA, 1, 1, 0, GL_RGBA,
                                      GL_UNSIGNED_BYTE, _))
              .Times(1)
              .RetiresOnSaturation();
        }
      }
    }
  }
  EXPECT_CALL(*gl, BindTexture(target, 0))
      .Times(1)
      .RetiresOnSaturation();
}

void TestHelper::SetupTextureManagerInitExpectations(
    ::gl::MockGLInterface* gl,
    bool is_es3_enabled,
    bool is_es3_capable,
    bool is_desktop_core_profile,
    const char* extensions,
    bool use_default_textures) {
  InSequence sequence;

  if (is_es3_capable) {
    EXPECT_CALL(*gl, BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0))
        .Times(1)
        .RetiresOnSaturation();
  }

  SetupTextureInitializationExpectations(
      gl, GL_TEXTURE_2D, use_default_textures);
  SetupTextureInitializationExpectations(
      gl, GL_TEXTURE_CUBE_MAP, use_default_textures);

  if (is_es3_enabled) {
    SetupTextureInitializationExpectations(
        gl, GL_TEXTURE_3D, use_default_textures);
    SetupTextureInitializationExpectations(
        gl, GL_TEXTURE_2D_ARRAY, use_default_textures);
  }

  bool ext_image_external = false;
  bool arb_texture_rectangle = is_desktop_core_profile;
  base::CStringTokenizer t(extensions, extensions + strlen(extensions), " ");
  while (t.GetNext()) {
    if (t.token() == "GL_OES_EGL_image_external") {
      ext_image_external = true;
      break;
    }
    if (t.token() == "GL_ARB_texture_rectangle") {
      arb_texture_rectangle = true;
      break;
    }
  }

  if (ext_image_external) {
    SetupTextureInitializationExpectations(
        gl, GL_TEXTURE_EXTERNAL_OES, use_default_textures);
  }
  if (arb_texture_rectangle) {
    SetupTextureInitializationExpectations(
        gl, GL_TEXTURE_RECTANGLE_ARB, use_default_textures);
  }
}

void TestHelper::SetupTextureDestructionExpectations(
    ::gl::MockGLInterface* gl,
    GLenum target,
    bool use_default_textures) {
  if (!use_default_textures)
    return;

  GLuint texture_id = 0;
  switch (target) {
    case GL_TEXTURE_2D:
      texture_id = kServiceDefaultTexture2dId;
      break;
    case GL_TEXTURE_3D:
      texture_id = kServiceDefaultTexture3dId;
      break;
    case GL_TEXTURE_2D_ARRAY:
      texture_id = kServiceDefaultTexture2dArrayId;
      break;
    case GL_TEXTURE_CUBE_MAP:
      texture_id = kServiceDefaultTextureCubemapId;
      break;
    case GL_TEXTURE_EXTERNAL_OES:
      texture_id = kServiceDefaultExternalTextureId;
      break;
    case GL_TEXTURE_RECTANGLE_ARB:
      texture_id = kServiceDefaultRectangleTextureId;
      break;
    default:
      NOTREACHED();
  }

  EXPECT_CALL(*gl, DeleteTextures(1, Pointee(texture_id)))
      .Times(1)
      .RetiresOnSaturation();
}

void TestHelper::SetupTextureManagerDestructionExpectations(
    ::gl::MockGLInterface* gl,
    bool is_es3_enabled,
    bool is_desktop_core_profile,
    const char* extensions,
    bool use_default_textures) {
  SetupTextureDestructionExpectations(gl, GL_TEXTURE_2D, use_default_textures);
  SetupTextureDestructionExpectations(
      gl, GL_TEXTURE_CUBE_MAP, use_default_textures);

  if (is_es3_enabled) {
    SetupTextureDestructionExpectations(
        gl, GL_TEXTURE_3D, use_default_textures);
    SetupTextureDestructionExpectations(
        gl, GL_TEXTURE_2D_ARRAY,use_default_textures);
  }

  bool ext_image_external = false;
  bool arb_texture_rectangle = false;
  base::CStringTokenizer t(extensions, extensions + strlen(extensions), " ");
  while (t.GetNext()) {
    if (t.token() == "GL_OES_EGL_image_external") {
      ext_image_external = true;
      break;
    }
    if (t.token() == "GL_ARB_texture_rectangle") {
      arb_texture_rectangle = true;
      break;
    }
  }

  if (ext_image_external) {
    SetupTextureDestructionExpectations(
        gl, GL_TEXTURE_EXTERNAL_OES, use_default_textures);
  }
  if (arb_texture_rectangle || is_desktop_core_profile) {
    SetupTextureDestructionExpectations(
        gl, GL_TEXTURE_RECTANGLE_ARB, use_default_textures);
  }

  EXPECT_CALL(*gl, DeleteTextures(TextureManager::kNumDefaultTextures, _))
      .Times(1)
      .RetiresOnSaturation();
}

void TestHelper::SetupContextGroupInitExpectations(
    ::gl::MockGLInterface* gl,
    const DisallowedFeatures& disallowed_features,
    const char* extensions,
    const char* gl_version,
    ContextType context_type,
    bool bind_generates_resource) {
  InSequence sequence;

  bool enable_es3 = !(context_type == CONTEXT_TYPE_OPENGLES2 ||
                      context_type == CONTEXT_TYPE_WEBGL1);

  gl::GLVersionInfo gl_info(gl_version, "", extensions);

  SetupFeatureInfoInitExpectationsWithGLVersion(gl, extensions, "", gl_version,
      context_type);
  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_RENDERBUFFER_SIZE, _))
      .WillOnce(SetArgumentPointee<1>(kMaxRenderbufferSize))
      .RetiresOnSaturation();
  if (strstr(extensions, "GL_EXT_framebuffer_multisample") ||
      strstr(extensions, "GL_EXT_multisampled_render_to_texture") ||
      gl_info.is_es3 || gl_info.is_desktop_core_profile) {
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_SAMPLES, _))
        .WillOnce(SetArgumentPointee<1>(kMaxSamples))
        .RetiresOnSaturation();
  } else if (strstr(extensions, "GL_IMG_multisampled_render_to_texture")) {
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_SAMPLES_IMG, _))
        .WillOnce(SetArgumentPointee<1>(kMaxSamples))
        .RetiresOnSaturation();
  }

  if (strstr(extensions, "GL_EXT_draw_buffers") ||
      strstr(extensions, "GL_ARB_draw_buffers") ||
      (gl_info.is_es3 && strstr(extensions, "GL_NV_draw_buffers")) ||
      gl_info.is_desktop_core_profile) {
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, _))
        .WillOnce(SetArgumentPointee<1>(8))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_DRAW_BUFFERS_ARB, _))
        .WillOnce(SetArgumentPointee<1>(8))
        .RetiresOnSaturation();
  }

  if (gl_info.IsAtLeastGL(3, 3) ||
      (gl_info.IsAtLeastGL(3, 2) &&
       strstr(extensions, "GL_ARB_blend_func_extended")) ||
      (gl_info.is_es && strstr(extensions, "GL_EXT_blend_func_extended"))) {
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT, _))
        .WillOnce(SetArgumentPointee<1>(8))
        .RetiresOnSaturation();
  }

  if (gl_info.is_es3_capable) {
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, _))
        .WillOnce(SetArgumentPointee<1>(kMaxTransformFeedbackSeparateAttribs))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, _))
        .WillOnce(SetArgumentPointee<1>(kMaxUniformBufferBindings))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, _))
        .WillOnce(SetArgumentPointee<1>(kUniformBufferOffsetAlignment))
        .RetiresOnSaturation();
  }

  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VERTEX_ATTRIBS, _))
      .WillOnce(SetArgumentPointee<1>(kNumVertexAttribs))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, _))
      .WillOnce(SetArgumentPointee<1>(kNumTextureUnits))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_TEXTURE_SIZE, _))
      .WillOnce(SetArgumentPointee<1>(kMaxTextureSize))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, _))
      .WillOnce(SetArgumentPointee<1>(kMaxCubeMapTextureSize))
      .RetiresOnSaturation();
  if (gl_info.is_es3_capable) {
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_3D_TEXTURE_SIZE, _))
        .WillOnce(SetArgumentPointee<1>(kMax3DTextureSize))
        .RetiresOnSaturation();
  }
  if (gl_info.is_es3_capable) {
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, _))
        .WillOnce(SetArgumentPointee<1>(kMaxArrayTextureLayers))
        .RetiresOnSaturation();
  }
  if (strstr(extensions, "GL_ARB_texture_rectangle") ||
      gl_info.is_desktop_core_profile) {
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_RECTANGLE_TEXTURE_SIZE, _))
        .WillOnce(SetArgumentPointee<1>(kMaxRectangleTextureSize))
        .RetiresOnSaturation();
  }
  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, _))
      .WillOnce(SetArgumentPointee<1>(kMaxTextureImageUnits))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, _))
      .WillOnce(SetArgumentPointee<1>(kMaxVertexTextureImageUnits))
      .RetiresOnSaturation();

  if (gl_info.is_es || gl_info.is_desktop_core_profile) {
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, _))
        .WillOnce(SetArgumentPointee<1>(kMaxFragmentUniformVectors))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VARYING_VECTORS, _))
        .WillOnce(SetArgumentPointee<1>(kMaxVaryingVectors))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, _))
        .WillOnce(SetArgumentPointee<1>(kMaxVertexUniformVectors))
        .RetiresOnSaturation();
  } else {
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, _))
        .WillOnce(SetArgumentPointee<1>(kMaxFragmentUniformComponents))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VARYING_FLOATS, _))
        .WillOnce(SetArgumentPointee<1>(kMaxVaryingFloats))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, _))
        .WillOnce(SetArgumentPointee<1>(kMaxVertexUniformComponents))
        .RetiresOnSaturation();
  }

  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VERTEX_OUTPUT_COMPONENTS, _))
      .Times(testing::Between(0, 1))
      .WillRepeatedly(SetArgumentPointee<1>(kMaxVertexOutputComponents))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_FRAGMENT_INPUT_COMPONENTS, _))
      .Times(testing::Between(0, 1))
      .WillRepeatedly(SetArgumentPointee<1>(kMaxFragmentInputComponents))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl, GetIntegerv(GL_MAX_PROGRAM_TEXEL_OFFSET, _))
      .Times(testing::Between(0, 1))
      .WillRepeatedly(SetArgumentPointee<1>(kMaxProgramTexelOffset))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl, GetIntegerv(GL_MIN_PROGRAM_TEXEL_OFFSET, _))
      .Times(testing::Between(0, 1))
      .WillRepeatedly(SetArgumentPointee<1>(kMinProgramTexelOffset))
      .RetiresOnSaturation();

  bool use_default_textures = bind_generates_resource;
  SetupTextureManagerInitExpectations(
      gl, enable_es3, gl_info.is_es3_capable, gl_info.is_desktop_core_profile,
      extensions, use_default_textures);
}

void TestHelper::SetupFeatureInfoInitExpectations(::gl::MockGLInterface* gl,
                                                  const char* extensions) {
  SetupFeatureInfoInitExpectationsWithGLVersion(gl, extensions, "", "",
      CONTEXT_TYPE_OPENGLES2);
}

void TestHelper::SetupFeatureInfoInitExpectationsWithGLVersion(
    ::gl::MockGLInterface* gl,
    const char* extensions,
    const char* gl_renderer,
    const char* gl_version,
    ContextType context_type) {
  InSequence sequence;

  bool enable_es3 = context_type == CONTEXT_TYPE_WEBGL2 ||
      context_type == CONTEXT_TYPE_OPENGLES3;

  EXPECT_CALL(*gl, GetString(GL_VERSION))
      .WillOnce(Return(reinterpret_cast<const uint8_t*>(gl_version)))
      .RetiresOnSaturation();

  // Persistent storage is needed for the split extension string.
  split_extensions_.clear();
  if (extensions) {
    split_extensions_ = base::SplitString(
        extensions, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  }

  gl::GLVersionInfo gl_info(gl_version, gl_renderer, extensions);
  if (!gl_info.is_es && gl_info.major_version >= 3) {
    EXPECT_CALL(*gl, GetIntegerv(GL_NUM_EXTENSIONS, _))
        .WillOnce(SetArgumentPointee<1>(split_extensions_.size()))
        .RetiresOnSaturation();
    for (size_t ii = 0; ii < split_extensions_.size(); ++ii) {
      EXPECT_CALL(*gl, GetStringi(GL_EXTENSIONS, ii))
          .WillOnce(Return(
              reinterpret_cast<const uint8_t*>(split_extensions_[ii].c_str())))
          .RetiresOnSaturation();
    }
  } else {
    EXPECT_CALL(*gl, GetString(GL_EXTENSIONS))
        .WillOnce(Return(reinterpret_cast<const uint8_t*>(extensions)))
        .RetiresOnSaturation();
  }

  EXPECT_CALL(*gl, GetString(GL_VERSION))
      .WillOnce(Return(reinterpret_cast<const uint8_t*>(gl_version)))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl, GetString(GL_RENDERER))
      .WillOnce(Return(reinterpret_cast<const uint8_t*>(gl_renderer)))
      .RetiresOnSaturation();

  if (enable_es3) {
    EXPECT_CALL(*gl, GetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, _))
      .WillOnce(SetArgPointee<1>(0))
      .RetiresOnSaturation();
  }

  if ((strstr(extensions, "GL_ARB_texture_float") ||
       gl_info.is_desktop_core_profile) ||
      (gl_info.is_es3 && strstr(extensions, "GL_OES_texture_float") &&
       strstr(extensions, "GL_EXT_color_buffer_float"))) {
    static const GLuint tx_ids[] = {101, 102};
    static const GLuint fb_ids[] = {103, 104};
    const GLsizei width = 16;
    EXPECT_CALL(*gl, GetIntegerv(GL_FRAMEBUFFER_BINDING, _))
        .WillOnce(SetArgumentPointee<1>(fb_ids[0]))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GetIntegerv(GL_TEXTURE_BINDING_2D, _))
        .WillOnce(SetArgumentPointee<1>(tx_ids[0]))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GenTextures(1, _))
        .WillOnce(SetArrayArgument<1>(tx_ids + 1, tx_ids + 2))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GenFramebuffersEXT(1, _))
        .WillOnce(SetArrayArgument<1>(fb_ids + 1, fb_ids + 2))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, BindTexture(GL_TEXTURE_2D, tx_ids[1]))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
        GL_NEAREST))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, width, 0,
        GL_RGBA, GL_FLOAT, _))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, BindFramebufferEXT(GL_FRAMEBUFFER, fb_ids[1]))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, FramebufferTexture2DEXT(GL_FRAMEBUFFER,
        GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tx_ids[1], 0))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
        .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
        .RetiresOnSaturation();
    GLenum status_rgba = GL_FRAMEBUFFER_COMPLETE;
    EXPECT_CALL(*gl, TexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, width, 0,
        GL_RGB, GL_FLOAT, _))
        .Times(1)
        .RetiresOnSaturation();
    if (gl_info.is_es3) {
      EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
          .WillOnce(Return(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT))
          .RetiresOnSaturation();
    } else {
      EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
          .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
          .RetiresOnSaturation();
    }

    if (status_rgba == GL_FRAMEBUFFER_COMPLETE && enable_es3) {
      EXPECT_CALL(*gl, TexImage2D(GL_TEXTURE_2D, 0, GL_R16F, width, width,
          0, GL_RED, GL_FLOAT, _))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl, TexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, width, width,
          0, GL_RG, GL_FLOAT, _))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl, TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, width,
          0, GL_RGBA, GL_FLOAT, _))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl, TexImage2D(GL_TEXTURE_2D, 0, GL_R32F, width, width,
          0, GL_RED, GL_FLOAT, _))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl, TexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, width, width,
          0, GL_RG, GL_FLOAT, _))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl, TexImage2D(GL_TEXTURE_2D, 0, GL_R11F_G11F_B10F,
          width, width, 0, GL_RGB, GL_FLOAT, _))
          .Times(1)
          .RetiresOnSaturation();
      EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
          .Times(1)
          .RetiresOnSaturation();
    }


    EXPECT_CALL(*gl, DeleteFramebuffersEXT(1, _))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, DeleteTextures(1, _))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, BindFramebufferEXT(GL_FRAMEBUFFER, fb_ids[0]))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, BindTexture(GL_TEXTURE_2D, tx_ids[0]))
        .Times(1)
        .RetiresOnSaturation();
#if DCHECK_IS_ON()
    EXPECT_CALL(*gl, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
#endif
  }

  if (strstr(extensions, "GL_EXT_draw_buffers") ||
      strstr(extensions, "GL_ARB_draw_buffers") ||
      (gl_info.is_es3 && strstr(extensions, "GL_NV_draw_buffers")) ||
      gl_info.is_desktop_core_profile) {
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, _))
        .WillOnce(SetArgumentPointee<1>(8))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GetIntegerv(GL_MAX_DRAW_BUFFERS_ARB, _))
        .WillOnce(SetArgumentPointee<1>(8))
        .RetiresOnSaturation();
  }

  if (gl_info.is_es3 || gl_info.is_desktop_core_profile ||
      strstr(extensions, "GL_EXT_texture_rg") ||
      (strstr(extensions, "GL_ARB_texture_rg"))) {
    static const GLuint tx_ids[] = {101, 102};
    static const GLuint fb_ids[] = {103, 104};
    const GLsizei width = 1;
    EXPECT_CALL(*gl, GetIntegerv(GL_FRAMEBUFFER_BINDING, _))
        .WillOnce(SetArgumentPointee<1>(fb_ids[0]))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GetIntegerv(GL_TEXTURE_BINDING_2D, _))
        .WillOnce(SetArgumentPointee<1>(tx_ids[0]))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GenTextures(1, _))
        .WillOnce(SetArrayArgument<1>(tx_ids + 1, tx_ids + 2))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, BindTexture(GL_TEXTURE_2D, tx_ids[1]))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, TexImage2D(GL_TEXTURE_2D, 0, _, width, width, 0,
        GL_RED_EXT, GL_UNSIGNED_BYTE, _))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GenFramebuffersEXT(1, _))
        .WillOnce(SetArrayArgument<1>(fb_ids + 1, fb_ids + 2))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, BindFramebufferEXT(GL_FRAMEBUFFER, fb_ids[1]))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, FramebufferTexture2DEXT(GL_FRAMEBUFFER,
        GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tx_ids[1], 0))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
        .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, DeleteFramebuffersEXT(1, _))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, DeleteTextures(1, _))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, BindFramebufferEXT(GL_FRAMEBUFFER, fb_ids[0]))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, BindTexture(GL_TEXTURE_2D, tx_ids[0]))
        .Times(1)
        .RetiresOnSaturation();
#if DCHECK_IS_ON()
    EXPECT_CALL(*gl, GetError())
        .WillOnce(Return(GL_NO_ERROR))
        .RetiresOnSaturation();
#endif
  }
}

void TestHelper::SetupExpectationsForClearingUniforms(::gl::MockGLInterface* gl,
                                                      UniformInfo* uniforms,
                                                      size_t num_uniforms) {
  for (size_t ii = 0; ii < num_uniforms; ++ii) {
    const UniformInfo& info = uniforms[ii];
    switch (info.type) {
    case GL_FLOAT:
      EXPECT_CALL(*gl, Uniform1fv(info.real_location, info.size, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_FLOAT_VEC2:
      EXPECT_CALL(*gl, Uniform2fv(info.real_location, info.size, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_FLOAT_VEC3:
      EXPECT_CALL(*gl, Uniform3fv(info.real_location, info.size, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_FLOAT_VEC4:
      EXPECT_CALL(*gl, Uniform4fv(info.real_location, info.size, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_INT:
    case GL_BOOL:
    case GL_SAMPLER_2D:
    case GL_SAMPLER_CUBE:
    case GL_SAMPLER_EXTERNAL_OES:
    case GL_SAMPLER_3D_OES:
    case GL_SAMPLER_2D_RECT_ARB:
    case GL_SAMPLER_2D_ARRAY:
      EXPECT_CALL(*gl, Uniform1iv(info.real_location, info.size, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_UNSIGNED_INT:
      EXPECT_CALL(*gl, Uniform1uiv(info.real_location, info.size, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_INT_VEC2:
    case GL_BOOL_VEC2:
      EXPECT_CALL(*gl, Uniform2iv(info.real_location, info.size, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_UNSIGNED_INT_VEC2:
      EXPECT_CALL(*gl, Uniform2uiv(info.real_location, info.size, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_INT_VEC3:
    case GL_BOOL_VEC3:
      EXPECT_CALL(*gl, Uniform3iv(info.real_location, info.size, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_UNSIGNED_INT_VEC3:
      EXPECT_CALL(*gl, Uniform3uiv(info.real_location, info.size, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_INT_VEC4:
    case GL_BOOL_VEC4:
      EXPECT_CALL(*gl, Uniform4iv(info.real_location, info.size, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_UNSIGNED_INT_VEC4:
      EXPECT_CALL(*gl, Uniform4uiv(info.real_location, info.size, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_FLOAT_MAT2:
      EXPECT_CALL(*gl, UniformMatrix2fv(
          info.real_location, info.size, false, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_FLOAT_MAT3:
      EXPECT_CALL(*gl, UniformMatrix3fv(
          info.real_location, info.size, false, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    case GL_FLOAT_MAT4:
      EXPECT_CALL(*gl, UniformMatrix4fv(
          info.real_location, info.size, false, _))
          .Times(1)
          .RetiresOnSaturation();
      break;
    default:
      NOTREACHED();
      break;
    }
  }
}

void TestHelper::SetupProgramSuccessExpectations(
    ::gl::MockGLInterface* gl,
    const FeatureInfo* feature_info,
    AttribInfo* attribs,
    size_t num_attribs,
    UniformInfo* uniforms,
    size_t num_uniforms,
    VaryingInfo* varyings,
    size_t num_varyings,
    ProgramOutputInfo* program_outputs,
    size_t num_program_outputs,
    GLuint service_id) {
  EXPECT_CALL(*gl,
      GetProgramiv(service_id, GL_LINK_STATUS, _))
      .WillOnce(SetArgumentPointee<2>(1))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl,
      GetProgramiv(service_id, GL_INFO_LOG_LENGTH, _))
      .WillOnce(SetArgumentPointee<2>(0))
      .RetiresOnSaturation();
  EXPECT_CALL(*gl,
      GetProgramiv(service_id, GL_ACTIVE_ATTRIBUTES, _))
      .WillOnce(SetArgumentPointee<2>(num_attribs))
      .RetiresOnSaturation();
  size_t max_attrib_len = 0;
  for (size_t ii = 0; ii < num_attribs; ++ii) {
    size_t len = strlen(attribs[ii].name) + 1;
    max_attrib_len = std::max(max_attrib_len, len);
  }
  EXPECT_CALL(*gl,
      GetProgramiv(service_id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, _))
      .WillOnce(SetArgumentPointee<2>(max_attrib_len))
      .RetiresOnSaturation();

  for (size_t ii = 0; ii < num_attribs; ++ii) {
    const AttribInfo& info = attribs[ii];
    EXPECT_CALL(*gl,
        GetActiveAttrib(service_id, ii,
                        max_attrib_len, _, _, _, _))
        .WillOnce(DoAll(
            SetArgumentPointee<3>(strlen(info.name)),
            SetArgumentPointee<4>(info.size),
            SetArgumentPointee<5>(info.type),
            SetArrayArgument<6>(info.name,
                                info.name + strlen(info.name) + 1)))
        .RetiresOnSaturation();
    if (!ProgramManager::HasBuiltInPrefix(info.name)) {
      EXPECT_CALL(*gl, GetAttribLocation(service_id, StrEq(info.name)))
          .WillOnce(Return(info.location))
          .RetiresOnSaturation();
    }
  }
  EXPECT_CALL(*gl,
      GetProgramiv(service_id, GL_ACTIVE_UNIFORMS, _))
      .WillOnce(SetArgumentPointee<2>(num_uniforms))
      .RetiresOnSaturation();

  if (num_uniforms > 0) {
    size_t max_uniform_len = 0;
    for (size_t ii = 0; ii < num_uniforms; ++ii) {
      size_t len = strlen(uniforms[ii].name) + 1;
      max_uniform_len = std::max(max_uniform_len, len);
    }
    EXPECT_CALL(*gl, GetProgramiv(service_id, GL_ACTIVE_UNIFORM_MAX_LENGTH, _))
        .WillOnce(SetArgumentPointee<2>(max_uniform_len))
        .RetiresOnSaturation();
    for (size_t ii = 0; ii < num_uniforms; ++ii) {
      const UniformInfo& info = uniforms[ii];
      EXPECT_CALL(*gl,
                  GetActiveUniform(service_id, ii, max_uniform_len, _, _, _, _))
          .WillOnce(DoAll(SetArgumentPointee<3>(strlen(info.name)),
                          SetArgumentPointee<4>(info.size),
                          SetArgumentPointee<5>(info.type),
                          SetArrayArgument<6>(
                              info.name, info.name + strlen(info.name) + 1)))
          .RetiresOnSaturation();

      if (info.real_location != -1) {
        EXPECT_CALL(*gl, GetUniformLocation(service_id, StrEq(info.name)))
            .WillOnce(Return(info.real_location))
            .RetiresOnSaturation();
      }
      if (info.size > 1) {
        std::string base_name = info.name;
        size_t array_pos = base_name.rfind("[0]");
        if (base_name.size() > 3 && array_pos == base_name.size() - 3) {
          base_name = base_name.substr(0, base_name.size() - 3);
        }
        for (GLsizei jj = 1; jj < info.size; ++jj) {
          std::string element_name(std::string(base_name) + "[" +
                                   base::IntToString(jj) + "]");
          EXPECT_CALL(*gl, GetUniformLocation(service_id, StrEq(element_name)))
              .WillOnce(Return(info.real_location + jj * 2))
              .RetiresOnSaturation();
        }
      }
    }
  }

  if (feature_info->feature_flags().chromium_path_rendering) {
    EXPECT_CALL(*gl, GetProgramInterfaceiv(service_id, GL_FRAGMENT_INPUT_NV,
                                           GL_ACTIVE_RESOURCES, _))
        .WillOnce(SetArgumentPointee<3>(int(num_varyings)))
        .RetiresOnSaturation();
    size_t max_varying_len = 0;
    for (size_t ii = 0; ii < num_varyings; ++ii) {
      size_t len = strlen(varyings[ii].name) + 1;
      max_varying_len = std::max(max_varying_len, len);
    }
    EXPECT_CALL(*gl, GetProgramInterfaceiv(service_id, GL_FRAGMENT_INPUT_NV,
                                           GL_MAX_NAME_LENGTH, _))
        .WillOnce(SetArgumentPointee<3>(int(max_varying_len)))
        .RetiresOnSaturation();
    for (size_t ii = 0; ii < num_varyings; ++ii) {
      VaryingInfo& info = varyings[ii];
      EXPECT_CALL(*gl, GetProgramResourceName(service_id, GL_FRAGMENT_INPUT_NV,
                                              ii, max_varying_len, _, _))
          .WillOnce(DoAll(SetArgumentPointee<4>(strlen(info.name)),
                          SetArrayArgument<5>(
                              info.name, info.name + strlen(info.name) + 1)))
          .RetiresOnSaturation();
      if (ProgramManager::HasBuiltInPrefix(info.name))
        continue;

        static const GLenum kPropsArray[] = {GL_LOCATION, GL_TYPE,
                                             GL_ARRAY_SIZE};
        static const size_t kPropsSize = arraysize(kPropsArray);
        EXPECT_CALL(
            *gl, GetProgramResourceiv(
                     service_id, GL_FRAGMENT_INPUT_NV, ii, kPropsSize,
                     _ /*testing::ElementsAreArray(kPropsArray, kPropsSize)*/,
                     kPropsSize, _, _))
            .WillOnce(testing::Invoke([info](GLuint, GLenum, GLuint, GLsizei,
                                             const GLenum*, GLsizei,
                                             GLsizei* length, GLint* params) {
              *length = kPropsSize;
              params[0] = info.real_location;
              params[1] = info.type;
              params[2] = info.size;
            }))
            .RetiresOnSaturation();
      }
  }
  if (feature_info->gl_version_info().is_es3_capable &&
      !feature_info->disable_shader_translator()) {
    for (size_t ii = 0; ii < num_program_outputs; ++ii) {
      ProgramOutputInfo& info = program_outputs[ii];
      if (ProgramManager::HasBuiltInPrefix(info.name))
        continue;

      EXPECT_CALL(*gl, GetFragDataLocation(service_id, StrEq(info.name)))
          .WillOnce(Return(info.color_name))
          .RetiresOnSaturation();
      if (feature_info->feature_flags().ext_blend_func_extended) {
        EXPECT_CALL(*gl, GetFragDataIndex(service_id, StrEq(info.name)))
            .WillOnce(Return(info.index))
            .RetiresOnSaturation();
      } else {
        // Test case must not use indices, or the context of the testcase has to
        // support the dual source blending.
        DCHECK(info.index == 0);
      }
    }
  }
}

void TestHelper::SetupShaderExpectations(::gl::MockGLInterface* gl,
                                         const FeatureInfo* feature_info,
                                         AttribInfo* attribs,
                                         size_t num_attribs,
                                         UniformInfo* uniforms,
                                         size_t num_uniforms,
                                         GLuint service_id) {
  InSequence s;

  EXPECT_CALL(*gl, LinkProgram(service_id)).Times(1).RetiresOnSaturation();

  SetupProgramSuccessExpectations(gl, feature_info, attribs, num_attribs,
                                  uniforms, num_uniforms, nullptr, 0, nullptr,
                                  0, service_id);
}

void TestHelper::SetupShaderExpectationsWithVaryings(
    ::gl::MockGLInterface* gl,
    const FeatureInfo* feature_info,
    AttribInfo* attribs,
    size_t num_attribs,
    UniformInfo* uniforms,
    size_t num_uniforms,
    VaryingInfo* varyings,
    size_t num_varyings,
    ProgramOutputInfo* program_outputs,
    size_t num_program_outputs,
    GLuint service_id) {
  InSequence s;

  EXPECT_CALL(*gl,
      LinkProgram(service_id))
      .Times(1)
      .RetiresOnSaturation();

  SetupProgramSuccessExpectations(
      gl, feature_info, attribs, num_attribs, uniforms, num_uniforms, varyings,
      num_varyings, program_outputs, num_program_outputs, service_id);
}

void TestHelper::DoBufferData(::gl::MockGLInterface* gl,
                              MockErrorState* error_state,
                              BufferManager* manager,
                              Buffer* buffer,
                              GLenum target,
                              GLsizeiptr size,
                              GLenum usage,
                              const GLvoid* data,
                              GLenum error) {
  EXPECT_CALL(*error_state, CopyRealGLErrorsToWrapper(_, _, _))
      .Times(1)
      .RetiresOnSaturation();
  if (manager->IsUsageClientSideArray(usage)) {
    EXPECT_CALL(*gl, BufferData(target, 0, _, usage))
        .Times(1)
        .RetiresOnSaturation();
  } else {
    EXPECT_CALL(*gl, BufferData(target, size, _, usage))
        .Times(1)
        .RetiresOnSaturation();
  }
  EXPECT_CALL(*error_state, PeekGLError(_, _, _))
      .WillOnce(Return(error))
      .RetiresOnSaturation();
  manager->DoBufferData(error_state, buffer, target, size, usage, data);
}

void TestHelper::SetTexParameteriWithExpectations(::gl::MockGLInterface* gl,
                                                  MockErrorState* error_state,
                                                  TextureManager* manager,
                                                  TextureRef* texture_ref,
                                                  GLenum pname,
                                                  GLint value,
                                                  GLenum error) {
  if (error == GL_NO_ERROR) {
    EXPECT_CALL(*gl, TexParameteri(texture_ref->texture()->target(),
                                   pname, value))
        .Times(1)
        .RetiresOnSaturation();
  } else if (error == GL_INVALID_ENUM) {
    EXPECT_CALL(*error_state, SetGLErrorInvalidEnum(_, _, _, value, _))
        .Times(1)
        .RetiresOnSaturation();
  } else {
    EXPECT_CALL(*error_state, SetGLErrorInvalidParami(_, _, error, _, _, _))
        .Times(1)
        .RetiresOnSaturation();
  }
  manager->SetParameteri("", error_state, texture_ref, pname, value);
}

// static
void TestHelper::SetShaderStates(
    ::gl::MockGLInterface* gl,
    Shader* shader,
    bool expected_valid,
    const std::string* const expected_log_info,
    const std::string* const expected_translated_source,
    const int* const expected_shader_version,
    const AttributeMap* const expected_attrib_map,
    const UniformMap* const expected_uniform_map,
    const VaryingMap* const expected_varying_map,
    const InterfaceBlockMap* const expected_interface_block_map,
    const OutputVariableList* const expected_output_variable_list,
    const NameMap* const expected_name_map) {
  const std::string empty_log_info;
  const std::string* log_info = (expected_log_info && !expected_valid) ?
      expected_log_info : &empty_log_info;
  const std::string empty_translated_source;
  const std::string* translated_source =
      (expected_translated_source && expected_valid) ?
          expected_translated_source : &empty_translated_source;
  int default_shader_version = 100;
  const int* shader_version = (expected_shader_version && expected_valid) ?
      expected_shader_version : &default_shader_version;
  const AttributeMap empty_attrib_map;
  const AttributeMap* attrib_map = (expected_attrib_map && expected_valid) ?
      expected_attrib_map : &empty_attrib_map;
  const UniformMap empty_uniform_map;
  const UniformMap* uniform_map = (expected_uniform_map && expected_valid) ?
      expected_uniform_map : &empty_uniform_map;
  const VaryingMap empty_varying_map;
  const VaryingMap* varying_map = (expected_varying_map && expected_valid) ?
      expected_varying_map : &empty_varying_map;
  const InterfaceBlockMap empty_interface_block_map;
  const InterfaceBlockMap* interface_block_map =
      (expected_interface_block_map && expected_valid) ?
      expected_interface_block_map : &empty_interface_block_map;
  const OutputVariableList empty_output_variable_list;
  const OutputVariableList* output_variable_list =
      (expected_output_variable_list && expected_valid)
          ? expected_output_variable_list
          : &empty_output_variable_list;
  const NameMap empty_name_map;
  const NameMap* name_map = (expected_name_map && expected_valid) ?
      expected_name_map : &empty_name_map;

  MockShaderTranslator* mock_translator = new MockShaderTranslator;
  scoped_refptr<ShaderTranslatorInterface> translator(mock_translator);
  EXPECT_CALL(*mock_translator, Translate(_,
                                          NotNull(),   // log_info
                                          NotNull(),   // translated_source
                                          NotNull(),   // shader_version
                                          NotNull(),   // attrib_map
                                          NotNull(),   // uniform_map
                                          NotNull(),   // varying_map
                                          NotNull(),   // interface_block_map
                                          NotNull(),   // output_variable_list
                                          NotNull()))  // name_map
      .WillOnce(DoAll(SetArgumentPointee<1>(*log_info),
                      SetArgumentPointee<2>(*translated_source),
                      SetArgumentPointee<3>(*shader_version),
                      SetArgumentPointee<4>(*attrib_map),
                      SetArgumentPointee<5>(*uniform_map),
                      SetArgumentPointee<6>(*varying_map),
                      SetArgumentPointee<7>(*interface_block_map),
                      SetArgumentPointee<8>(*output_variable_list),
                      SetArgumentPointee<9>(*name_map), Return(expected_valid)))
      .RetiresOnSaturation();
  if (expected_valid) {
    EXPECT_CALL(*gl, ShaderSource(shader->service_id(), 1, _, NULL))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, CompileShader(shader->service_id()))
        .Times(1)
        .RetiresOnSaturation();
    EXPECT_CALL(*gl, GetShaderiv(shader->service_id(),
                                 GL_COMPILE_STATUS,
                                 NotNull()))  // status
        .WillOnce(SetArgumentPointee<2>(GL_TRUE))
        .RetiresOnSaturation();
  }
  shader->RequestCompile(translator, Shader::kGL);
  shader->DoCompile();
}

// static
void TestHelper::SetShaderStates(::gl::MockGLInterface* gl,
                                 Shader* shader,
                                 bool valid) {
  SetShaderStates(gl, shader, valid, nullptr, nullptr, nullptr, nullptr,
                  nullptr, nullptr, nullptr, nullptr, nullptr);
}

// static
sh::Attribute TestHelper::ConstructAttribute(
    GLenum type, GLint array_size, GLenum precision,
    bool static_use, const std::string& name) {
  return ConstructShaderVariable<sh::Attribute>(
      type, array_size, precision, static_use, name);
}

// static
sh::Uniform TestHelper::ConstructUniform(
    GLenum type, GLint array_size, GLenum precision,
    bool static_use, const std::string& name) {
  return ConstructShaderVariable<sh::Uniform>(
      type, array_size, precision, static_use, name);
}

// static
sh::Varying TestHelper::ConstructVarying(
    GLenum type, GLint array_size, GLenum precision,
    bool static_use, const std::string& name) {
  return ConstructShaderVariable<sh::Varying>(
      type, array_size, precision, static_use, name);
}

sh::OutputVariable TestHelper::ConstructOutputVariable(
    GLenum type,
    GLint array_size,
    GLenum precision,
    bool static_use,
    const std::string& name) {
  return ConstructShaderVariable<sh::OutputVariable>(
      type, array_size, precision, static_use, name);
}

}  // namespace gles2
}  // namespace gpu

