text: /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
  /*
   *     Copyright 2010-2020 Couchbase, Inc.
   *
   *   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.
   */
  #ifndef TESTS_MOCK_ENVIRONMENT_H
  #define TESTS_MOCK_ENVIRONMENT_H 1
  
  #include "config.h"
  #include <gtest/gtest.h>
  #include <libcouchbase/couchbase.h>
  #include "serverparams.h"
  #include "contrib/lcb-jsoncpp/lcb-jsoncpp.h"
  
  class HandleWrap
  {
  
      friend class MockEnvironment;
  
    public:
      lcb_INSTANCE *getLcb() const
      {
          return instance;
      }
  
      void destroy();
  
      // Don't ever allow copying. C++0x allows = 0, though
      HandleWrap operator=(const HandleWrap &)
      {
          fprintf(stderr, "Can't copy this object around!\n");
          abort();
          return HandleWrap();
      }
  
      HandleWrap() : instance(NULL), iops(NULL) {}
      virtual ~HandleWrap();
  
    private:
      lcb_INSTANCE *instance;
      lcb_io_opt_t iops;
  };
  
  class MockCommand
  {
  #define XMOCKCMD(X)                                                                                                    \
      X(FAILOVER)                                                                                                        \
      X(RESPAWN)                                                                                                         \
      X(HICCUP)                                                                                                          \
      X(TRUNCATE)                                                                                                        \
      X(MOCKINFO)                                                                                                        \
      X(PERSIST)                                                                                                         \
      X(CACHE)                                                                                                           \
      X(UNPERSIST)                                                                                                       \
      X(UNCACHE)                                                                                                         \
      X(ENDURE)                                                                                                          \
      X(PURGE)                                                                                                           \
      X(KEYINFO)                                                                                                         \
      X(GET_MCPORTS)                                                                                                     \
      X(SET_CCCP)                                                                                                        \
      X(REGEN_VBCOORDS)                                                                                                  \
      X(RESET_QUERYSTATE)                                                                                                \
      X(OPFAIL)                                                                                                          \
      X(START_RETRY_VERIFY)                                                                                              \
      X(CHECK_RETRY_VERIFY)                                                                                              \
      X(SET_ENHANCED_ERRORS)                                                                                             \
      X(SET_COMPRESSION)                                                                                                 \
      X(SET_SASL_MECHANISMS)
  
    public:
      enum Code {
  #define X(cc) cc,
          XMOCKCMD(X)
  #undef X
              _NONE
      };
  
      static std::string GetName(Code code)
      {
  
  #define X(cc)                                                                                                          \
      if (code == cc) {                                                                                                  \
          return #cc;                                                                                                    \
      }
          XMOCKCMD(X)
  #undef X
  
          abort();
          return "";
      }
  
      MockCommand(Code code);
  
      // Various methods to set a field in the payload
      template < typename T > void set(const std::string &s, const T &v)
      {
          (*payload)[s] = v;
      }
      virtual ~MockCommand();
  
      // Encodes the command in a form suitable for sending over the network
      std::string encode();
  
    protected:
      Code code;
      std::string name;
      Json::Value command;
      Json::Value *payload;
      virtual void finalizePayload() {}
  
    private:
      MockCommand(const MockCommand &other);
  };
  
  class MockKeyCommand : public MockCommand
  {
    public:
      MockKeyCommand(Code code, std::string &key) : MockCommand(code), vbucket(-1)
      {
          this->key = key;
      }
  
      const std::string &getKey() const
      {
          return key;
      }
  
      short vbucket;
      std::string bucket;
      std::string key;
  
    protected:
      virtual void finalizePayload();
  };
  
  class MockMutationCommand : public MockKeyCommand
  {
    public:
      MockMutationCommand(Code code, std::string &key)
          : MockKeyCommand(code, key), onMaster(false), replicaCount(0), cas(0)
      {
      }
  
      bool onMaster;
      int replicaCount;
      std::vector< int > replicaList;
      lcb_uint64_t cas;
      std::string value;
  
    protected:
      virtual void finalizePayload();
  };
  
  class MockBucketCommand : public MockCommand
  {
    public:
      MockBucketCommand(Code code, int index, std::string bucketstr = "default") : MockCommand(code)
      {
          ix = index;
          bucket = bucketstr;
      }
  
    protected:
      virtual void finalizePayload();
      int ix;
      std::string bucket;
  };
  
  class MockOpfailCommand : public MockCommand
  {
    public:
      MockOpfailCommand(uint16_t errcode, int index, int count = -1, std::string bucketstr = "default")
          : MockCommand(OPFAIL)
      {
          set("count", count);
          set("bucket", bucketstr);
          set("code", errcode);
  
          Json::Value srvlist(Json::arrayValue);
          srvlist.append(index);
          set("servers", srvlist);
      }
  };
  
  class MockOpFailClearCommand : public MockCommand
  {
    public:
      MockOpFailClearCommand(size_t nservers, std::string bucketstr = "default") : MockCommand(OPFAIL)
      {
          set("count", -1);
          set("bucket", bucketstr);
          set("code", 0);
  
          Json::Value srvlist(Json::arrayValue);
          for (size_t ii = 0; ii < nservers; ++ii) {
              srvlist.append(static_cast< int >(ii));
          }
          set("servers", srvlist);
      }
  };
  
  class MockResponse
  {
    public:
      MockResponse() {}
      ~MockResponse();
      void assign(const std::string &s);
  
      bool isOk();
      const Json::Value &getRawResponse()
      {
          return jresp;
      }
      const Json::Value &constResp() const
      {
          return jresp;
      }
  
    protected:
      Json::Value jresp;
      friend std::ostream &operator<<(std::ostream &, const MockResponse &);
  
    private:
      MockResponse(const MockResponse &);
  };
  
  class MockEnvironment : public ::testing::Environment
  {
    public:
      enum ServerVersion {
          VERSION_UNKNOWN = 0,
          VERSION_40 = 4,
          VERSION_41 = 5,
          VERSION_45 = 6,
          VERSION_46 = 7,
          VERSION_50 = 8,
          VERSION_55 = 9,
          VERSION_60 = 10,
          VERSION_65 = 11,
          VERSION_66 = 12,
          VERSION_70 = 13
      };
  
      virtual void SetUp();
      virtual void TearDown();
  
      static MockEnvironment *getInstance(void);
      static void Reset();
  
      /**
       * Make a connect structure you may utilize to connect to
       * the backend we're running the tests towards.
       *
       * @param crst the create structure to fill in
       * @param io the io ops to use (pass NULL if you don't have a
       *           special io ops you want to use
       */
      void makeConnectParams(lcb_CREATEOPTS *&crst, lcb_io_opt_t io = NULL, lcb_INSTANCE_TYPE type = LCB_TYPE_BUCKET)
      {
          serverParams.makeConnectParams(crst, io, type);
      }
  
      /**
       * Get the number of nodes used in the backend
       */
      int getNumNodes(void) const
      {
          return numNodes;
      }
  
      /**
       * Are we currently using a real cluster as the backend, or
       * are we using the mock server.
       *
       * You should try your very best to avoid using this variable, and
       * rather extend the mock server to support the requested feature.
       */
      bool isRealCluster(void) const
      {
          return realCluster;
      }
  
      /**
       * Simulate node failover. In this case mock will disable server
       * corresponding given index an push new configuration. No data
       * rebalancing implemented on the mock.
       *
       * @param index the index of the node on the mock
       * @param bucket the name of the bucket
       */
      void failoverNode(int index, std::string bucket = "default", bool rebalance = true);
  
      /**
       * Simulate node reconvering. In this case mock will enable server
       * corresponding given index an push new configuration. No data
       * rebalancing implemented on the mock.
       *
       * @param index the index of the node on the mock
       * @param bucket the name of the bucket
       */
      void respawnNode(int index, std::string bucket = "default");
  
      /**
       * Regenerate existing UUIDs and sequence numbers on the cluster to
       * simulate a dcp-style failover. This is a separate command as triggering
       * this during a "Normal" failover severly slows down the mock.
       *
       * @param bucket
       */
      void regenVbCoords(std::string bucket = "default");
  
      /**
       * Retrieve the memcached listening ports for a given bucket
       * @param bucket the bucket for which to retrieve memcached port into
       * @return a vector of ports to use.
       */
      std::vector< int > getMcPorts(std::string bucket = "default");
  
      /**
       * Enable SASL mechanisms on the mock cluster
       * @param mechanisms list of mechanisms to enable
       * @param bucket the bucket on which to enable these mechanisms
       * @param nodes a list of by-index nodes on which to enable mechanisms. If NULL
       * then all nodes are enabled
       */
      void setSaslMechs(std::vector< std::string > &mechanisms, std::string bucket = "",
                        const std::vector< int > *nodes = NULL);
  
      /**
       * Enable CCCP on the mock cluster
       * @param bucket the bucket on which to enable CCCP
       * @param nodes a list of by-index nodes on which to enable CCCP. If NULL
       * then all nodes are enabled
       * @param bucket the bucket on which to
       */
      void setCCCP(bool enabled, std::string bucket = "", const std::vector< int > *nodes = NULL);
  
      /**
       * Enable enhanced errors on the mock cluster
       *
       * This includes generation event id (ref), and setting context for some errors
       * .
       * @param bucket the bucket on which to enable enhanced errors
       * @param nodes a list of by-index nodes on which to enable Enhanced Errors. If NULL
       * then all nodes are enabled
       */
      void setEnhancedErrors(bool enabled, std::string bucket = "", const std::vector< int > *nodes = NULL);
  
      /**
       * Change compression mode on the server
       *
       * @param mode compression mode ("off", "passive", "active")
       * @param bucket the bucket on which to enable compression
       * @param nodes a list of by-index nodes on which to enable compression. If NULL
       * then all nodes are enabled
       */
      void setCompression(std::string mode, std::string bucket = "", const std::vector< int > *nodes = NULL);
  
      const Json::Value getKeyInfo(std::string key, std::string bucket = "");
  
      /**
       * Create a connection to the mock/real server.
       *
       * The instance will be initialized with the the connect parameters
       * to either the mock or a real server (just like makeConnectParams),
       * and call lcb_create. The io instance will be stored in the instance
       * cookie so you may grab it from there.
       *
       * You should call lcb_destroy on the instance when you're done
       * using it.
       *
       * @param instance the instane to create
       */
      void createConnection(lcb_INSTANCE **instance);
  
      void createConnection(HandleWrap &handle, lcb_INSTANCE **instance);
      void createConnection(HandleWrap &handle, lcb_INSTANCE **instance, const lcb_CREATEOPTS *options);
  
      /**
       * Setup mock to split response in two parts: send first "offset" bytes
       * immediately and send the rest after "msecs" milliseconds.
       *
       * @param msecs the number of milliseconds to wait before sending the
       *              rest of the packet.
       * @param offset the number of the bytes to send in first before delay
       */
      void hiccupNodes(int msecs, int offset);
  
      ServerVersion getServerVersion(void) const
      {
          return serverVersion;
      }
  
      void setServerVersion(ServerVersion ver)
      {
          serverVersion = ver;
      }
  
      void sendCommand(MockCommand &cmd);
      void getResponse(MockResponse &resp);
      void getResponse()
      {
          MockResponse tmp;
          getResponse(tmp);
      }
  
      bool hasFeature(const char *feature)
      {
          return featureRegistry.find(feature) != featureRegistry.end();
      }
  
      static void printSkipMessage(std::string file, int line, std::string reason)
      {
          std::cerr << "Skipping " << file << ":" << std::dec << line;
          std::cerr << " (" << reason << ")";
          std::cerr << std::endl;
      }
  
      MockEnvironment(const char **argv, std::string name = "default");
      virtual ~MockEnvironment();
      void postCreate(lcb_INSTANCE *instance);
  
    protected:
      /**
       * Protected destructor to make it to a singleton
       */
      MockEnvironment();
      /**
       * Handle to the one and only instance of the mock environment
       */
      static MockEnvironment *instance;
  
      void bootstrapRealCluster();
      const struct test_server_info *mock;
      ServerParams serverParams;
      int numNodes;
      bool realCluster;
      ServerVersion serverVersion;
      const char *http;
      lcb_io_opt_st *iops;
      std::set< std::string > featureRegistry;
      std::string bucketName;
      std::string userName;
      const char **argv;
      void clearAndReset();
  
    private:
      lcb_INSTANCE *innerClient;
      void setupInnerClient();
      void init();
  };
  
  #define LCB_TEST_REQUIRE_CLUSTER_VERSION(v)                                                                            \
      if (!MockEnvironment::getInstance()->isRealCluster()) {                                                            \
          MockEnvironment::printSkipMessage(__FILE__, __LINE__, "need real cluster");                                    \
          return;                                                                                                        \
      }                                                                                                                  \
      if (MockEnvironment::getInstance()->getServerVersion() < v) {                                                      \
          MockEnvironment::printSkipMessage(__FILE__, __LINE__, "needs higher cluster version");                         \
          return;                                                                                                        \
      }
  
  #define LCB_TEST_REQUIRE_FEATURE(s)                                                                                    \
      if (!MockEnvironment::getInstance()->hasFeature(s)) {                                                              \
          MockEnvironment::printSkipMessage(__FILE__, __LINE__, "Feature " s " missing in server implementation");       \
          return;                                                                                                        \
      }
  
  #define CLUSTER_VERSION_IS_HIGHER_THAN(v)                                                                              \
      (MockEnvironment::getInstance()->isRealCluster() && MockEnvironment::getInstance()->getServerVersion() >= v)
  
  #define SKIP_IF_CLUSTER_VERSION_IS_HIGHER_THAN(v)                                                                      \
      if (CLUSTER_VERSION_IS_HIGHER_THAN(v)) {                                                                           \
          MockEnvironment::printSkipMessage(__FILE__, __LINE__, "needs lower cluster version");                          \
          return;                                                                                                        \
      }
  
  #define CLUSTER_VERSION_IS_LOWER_THAN(v)                                                                               \
      (MockEnvironment::getInstance()->isRealCluster() && MockEnvironment::getInstance()->getServerVersion() < v)
  
  #define SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(v)                                                                       \
      if (CLUSTER_VERSION_IS_LOWER_THAN(v)) {                                                                            \
          MockEnvironment::printSkipMessage(__FILE__, __LINE__, "needs higher cluster version");                         \
          return;                                                                                                        \
      }
  #endif
  