/*
 * Copyright 2009-present MongoDB, 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.
 */

#include "result.h"

#include "bson/bson.h"
#include "bsonutil/bson-parser.h"
#include "bsonutil/bson-match.h"
#include "mongoc-error-private.h"
#include "test-conveniences.h"
#include "util.h"
#include "TestSuite.h"
#include <common-string-private.h>

struct _result_t {
   bool ok;
   bson_val_t *value;
   bson_error_t error;
   /* For a write operation, reply is the original unmodified write result for a
      write operation.
      For a read operation, reply is an optional server reply. */
   bson_t *reply;
   char *str;
   bool array_of_root_docs;
   // For a mongoc_client_bulkwrite operation, `write_errors` and
   // `write_concern_errors` are optionally set.
   bson_t *write_errors;
   bson_t *write_concern_errors;
};

result_t *
result_new (void)
{
   return bson_malloc0 (sizeof (result_t));
}

static void
_result_init (result_t *result, const bson_val_t *value, const bson_t *reply, const bson_error_t *error)
{
   mcommon_string_t *str;

   str = mcommon_string_new ("");

   if (value) {
      result->value = bson_val_copy (value);
      mcommon_string_append_printf (str, "value=%s ", bson_val_to_json (value));
   }

   if (reply) {
      char *reply_str = bson_as_canonical_extended_json (reply, NULL);

      mcommon_string_append_printf (str, "reply=%s ", reply_str);
      result->reply = bson_copy (reply);
      bson_free (reply_str);
   }

   mcommon_string_append_printf (str, "bson_error=%s", error->message);
   memcpy (&result->error, error, sizeof (bson_error_t));
   result->ok = (error->code == 0);
   result->str = mcommon_string_free (str, false);
   result->write_errors = bson_new ();
   result->write_concern_errors = bson_new ();
}

void
result_destroy (result_t *result)
{
   if (!result) {
      return;
   }
   bson_val_destroy (result->value);
   bson_destroy (result->reply);
   bson_free (result->str);
   bson_destroy (result->write_errors);
   bson_destroy (result->write_concern_errors);
   bson_free (result);
}

const char *
result_to_string (result_t *result)
{
   return result->str;
}

bson_val_t *
result_get_val (result_t *result)
{
   return result->value;
}

bson_t *
rewrite_upserted_ids (bson_t *mongoc_upserted_ids)
{
   bson_t *upserted_ids;
   bson_iter_t iter;

   upserted_ids = bson_new ();
   BSON_FOREACH (mongoc_upserted_ids, iter)
   {
      bson_t el;
      bson_parser_t *el_bp;
      int64_t *index;
      bson_val_t *id;
      char storage[16];
      const char *key;

      bson_iter_bson (&iter, &el);
      el_bp = bson_parser_new ();
      bson_parser_int (el_bp, "index", &index);
      bson_parser_any (el_bp, "_id", &id);
      bson_parser_parse_or_assert (el_bp, &el);
      bson_uint32_to_string ((uint32_t) *index, &key, storage, sizeof (storage));
      BSON_APPEND_VALUE (upserted_ids, key, bson_val_to_value (id));
      bson_parser_destroy_with_parsed_fields (el_bp);
   }

   return upserted_ids;
}

bson_t *
rewrite_bulk_write_result (const bson_t *bulk_write_result)
{
   bson_t *const res = bson_new ();
   if (!bson_empty0 (bulk_write_result)) {
      BCON_APPEND (res,
                   "insertedCount",
                   BCON_INT32 (bson_lookup_int32 (bulk_write_result, "nInserted")),
                   "deletedCount",
                   BCON_INT32 (bson_lookup_int32 (bulk_write_result, "nRemoved")),
                   "matchedCount",
                   BCON_INT32 (bson_lookup_int32 (bulk_write_result, "nMatched")),
                   "modifiedCount",
                   BCON_INT32 (bson_lookup_int32 (bulk_write_result, "nModified")),
                   "upsertedCount",
                   BCON_INT32 (bson_lookup_int32 (bulk_write_result, "nUpserted")));

      if (bson_has_field (bulk_write_result, "upserted")) {
         bson_t *const upserted_ids = bson_lookup_bson (bulk_write_result, "upserted");
         bson_t *const rewritten_upserted_ids = rewrite_upserted_ids (upserted_ids);
         BSON_APPEND_DOCUMENT (res, "upsertedIds", rewritten_upserted_ids);
         bson_destroy (upserted_ids);
         bson_destroy (rewritten_upserted_ids);
      } else {
         /* upsertedIds is a required field in BulkWriteResult, so append an
          * empty document even if no documents were upserted. */
         bson_t empty = BSON_INITIALIZER;
         BSON_APPEND_DOCUMENT (res, "upsertedIds", &empty);
      }
   }
   return res;
}

void
result_from_bulk_write (result_t *result, const bson_t *reply, const bson_error_t *error)
{
   bson_t *const write_result = rewrite_bulk_write_result (reply);
   bson_val_t *const val = bson_val_from_bson (write_result);

   _result_init (result, val, reply, error);

   bson_val_destroy (val);
   bson_destroy (write_result);
}

void
result_from_insert_one (result_t *result, const bson_t *reply, const bson_error_t *error)
{
   bson_t *write_result;
   bson_val_t *val;

   write_result = bson_new ();
   if (!bson_empty (reply)) {
      BCON_APPEND (write_result, "insertedCount", BCON_INT32 (bson_lookup_int32 (reply, "insertedCount")));
   }

   val = bson_val_from_bson (write_result);
   _result_init (result, val, reply, error);
   bson_val_destroy (val);
   bson_destroy (write_result);
}

void
result_from_insert_many (result_t *result, const bson_t *reply, const bson_error_t *error)
{
   bson_t *write_result;
   bson_val_t *val;

   write_result = bson_new ();
   if (!bson_empty (reply)) {
      BCON_APPEND (write_result,
                   "insertedCount",
                   BCON_INT32 (bson_lookup_int32 (reply, "insertedCount")),
                   "deletedCount",
                   BCON_INT32 (0),
                   "matchedCount",
                   BCON_INT32 (0),
                   "modifiedCount",
                   BCON_INT32 (0),
                   "upsertedCount",
                   BCON_INT32 (0),
                   "upsertedIds",
                   "{",
                   "}");
   }

   val = bson_val_from_bson (write_result);
   _result_init (result, val, reply, error);
   bson_val_destroy (val);
   bson_destroy (write_result);
}

void
result_from_update_or_replace (result_t *result, const bson_t *reply, const bson_error_t *error)
{
   bson_t *write_result;
   bson_val_t *val;
   bson_iter_t iter;

   write_result = bson_new ();
   if (!bson_empty (reply)) {
      BCON_APPEND (write_result,
                   "matchedCount",
                   BCON_INT32 (bson_lookup_int32 (reply, "matchedCount")),
                   "modifiedCount",
                   BCON_INT32 (bson_lookup_int32 (reply, "modifiedCount")),
                   "upsertedCount",
                   BCON_INT32 (bson_lookup_int32 (reply, "upsertedCount")));

      if (bson_iter_init_find (&iter, reply, "upsertedId")) {
         BSON_APPEND_VALUE (write_result, "upsertedId", bson_iter_value (&iter));
      }
   }

   val = bson_val_from_bson (write_result);
   _result_init (result, val, reply, error);
   bson_val_destroy (val);
   bson_destroy (write_result);
}

void
result_from_delete (result_t *result, const bson_t *reply, const bson_error_t *error)
{
   bson_t *write_result;
   bson_val_t *val;

   write_result = bson_new ();
   if (!bson_empty (reply)) {
      BCON_APPEND (write_result, "deletedCount", BCON_INT32 (bson_lookup_int32 (reply, "deletedCount")));
   }

   val = bson_val_from_bson (write_result);
   _result_init (result, val, reply, error);
   bson_val_destroy (val);
   bson_destroy (write_result);
}

void
result_from_distinct (result_t *result, const bson_t *reply, const bson_error_t *error)
{
   bson_val_t *val = NULL;
   bson_iter_t iter;

   if (!bson_empty (reply)) {
      if (bson_iter_init_find (&iter, reply, "values")) {
         val = bson_val_from_iter (&iter);
      }
   }

   _result_init (result, val, reply, error);
   bson_val_destroy (val);
}

void
result_from_cursor (result_t *result, mongoc_cursor_t *cursor)
{
   bson_error_t error = {0};
   const bson_t *reply = NULL;
   bson_t *documents = bson_new ();
   uint32_t i = 0;
   const bson_t *doc;
   bson_val_t *val;

   while (mongoc_cursor_next (cursor, &doc)) {
      const char *key;
      char storage[16];

      bson_uint32_to_string (i, &key, storage, sizeof (storage) / sizeof (char));
      BSON_APPEND_DOCUMENT (documents, key, doc);
      i++;
   }

   mongoc_cursor_error_document (cursor, &error, &reply);
   val = bson_val_from_array (documents);

   _result_init (result, val, (bson_t *) reply, &error);
   result->array_of_root_docs = true;
   bson_destroy (documents);
   bson_val_destroy (val);
}

void
result_from_val_and_reply (result_t *result, const bson_val_t *val, const bson_t *reply, const bson_error_t *error)
{
   _result_init (result, val, reply, error);
}

void
result_from_ok (result_t *result)
{
   bson_error_t error = {0};
   _result_init (result, NULL, NULL, &error);
}

bool
result_check (result_t *result, entity_map_t *em, bson_val_t *expect_result, bson_t *expect_error, bson_error_t *error)
{
   bool ret = false;
   bson_parser_t *parser = NULL;
   bool *is_error;
   bool *is_client_error;
   char *error_contains;
   int64_t *error_code;
   char *error_code_name;
   bson_t *error_labels_contain;
   bson_t *error_labels_omit;
   bson_val_t *error_expect_result;
   bson_val_t *error_response;
   bson_t *write_errors = NULL;
   bson_t *write_concern_errors = NULL;

   if (!expect_result && !expect_error) {
      if (!result->ok) {
         test_set_error (error, "expected success, but got error: %s", result->error.message);
         goto done;
      }
      ret = true;
      goto done;
   }

   /* check result. */
   if (expect_result) {
      if (!result->ok) {
         if (!bson_empty (result->write_errors)) {
            char *as_json = bson_as_canonical_extended_json (result->write_errors, NULL);
            test_diagnostics_error_info ("Write errors: %s", as_json);
            bson_free (as_json);
         }
         if (!bson_empty (result->write_concern_errors)) {
            char *as_json = bson_as_canonical_extended_json (result->write_concern_errors, NULL);
            test_diagnostics_error_info ("Write concern errors: %s", as_json);
            bson_free (as_json);
         }
         test_set_error (error, "expected result, but got error: %s", result->error.message);
         goto done;
      }
      if (!entity_map_match (em, expect_result, result->value, result->array_of_root_docs, error)) {
         test_diagnostics_error_info ("Result mismatch:\n"
                                      "Expected: %s\n"
                                      "Actual:   %s\n",
                                      bson_val_to_json (expect_result),
                                      bson_val_to_json (result->value));
         goto done;
      }
   }

   if (expect_error) {
      parser = bson_parser_new ();
      bson_parser_bool_optional (parser, "isError", &is_error);
      bson_parser_bool_optional (parser, "isClientError", &is_client_error);
      bson_parser_utf8_optional (parser, "errorContains", &error_contains);
      bson_parser_int_optional (parser, "errorCode", &error_code);
      bson_parser_utf8_optional (parser, "errorCodeName", &error_code_name);
      bson_parser_array_optional (parser, "errorLabelsContain", &error_labels_contain);
      bson_parser_array_optional (parser, "errorLabelsOmit", &error_labels_omit);
      bson_parser_any_optional (parser, "expectResult", &error_expect_result);
      bson_parser_any_optional (parser, "errorResponse", &error_response);
      bson_parser_doc_optional (parser, "writeErrors", &write_errors);
      bson_parser_array_optional (parser, "writeConcernErrors", &write_concern_errors);
      if (!bson_parser_parse (parser, expect_error, error)) {
         goto done;
      }

      MONGOC_DEBUG ("expected error");

      if (result->ok) {
         test_set_error (error, "expected error, but no error: %s", bson_val_to_json (result->value));
         goto done;
      }

      if (is_client_error && *is_client_error) {
         /* from errors.rst: "In Version 2, error codes originating on the
          * server always have error domain ``MONGOC_ERROR_SERVER`` or
          * ``MONGOC_ERROR_WRITE_CONCERN``" */
         if (result->error.domain == MONGOC_ERROR_SERVER || result->error.domain == MONGOC_ERROR_WRITE_CONCERN_ERROR) {
            test_set_error (error,
                            "expected client side error, but got: %" PRIu32 ", %" PRIu32,
                            result->error.domain,
                            result->error.code);
            goto done;
         }
      }

      if (error_contains) {
         if (strstr (result->error.message, error_contains) == NULL) {
            test_set_error (
               error, "expected error to contain \"%s\", but got: \"%s\"", error_contains, result->error.message);
            goto done;
         }
      }

      if (error_code) {
         if ((*error_code) != (int64_t) result->error.code) {
            test_set_error (
               error, "expected error code %" PRIi64 ", but got: %" PRIi64, *error_code, (int64_t) result->error.code);
            goto done;
         }
      }

      /* Waiting on CDRIVER-3147
      if (error_code_name) {
         bson_iter_t iter;

         if (!result->reply) {
            test_set_error (error, "%s", "expected error code name, but no error
      reply set"); goto done;
         }

         if (!bson_iter_init_find (&iter, result->reply, "codeName") ||
             !BSON_ITER_HOLDS_UTF8 (&iter)) {
            test_set_error (error, "utf8 codeName not found in error reply: %s",
      tmp_json (result->reply)); goto done;
         }

         if (0 !=
             bson_strcasecmp (bson_iter_utf8 (&iter, NULL), error_code_name)) {
            test_set_error (error, "expected codeName: %s, got: %s",
      error_code_name, bson_iter_utf8 (&iter, NULL)); goto done;
         }
      }
      */

      if (error_labels_contain) {
         bson_iter_t iter;

         if (!result->reply) {
            test_set_error (error, "%s", "expected error to contain labels, but got no error document");
            goto done;
         }

         BSON_FOREACH (error_labels_contain, iter)
         {
            const char *label;

            if (!BSON_ITER_HOLDS_UTF8 (&iter)) {
               test_set_error (
                  error, "expected UTF8 error label, got: %s", bson_type_to_string (bson_iter_type (&iter)));
               goto done;
            }

            label = bson_iter_utf8 (&iter, NULL);
            if (!mongoc_error_has_label (result->reply, label)) {
               test_set_error (
                  error, "expected error to contain label: %s, but got: %s", label, tmp_json (result->reply));
               goto done;
            }
         }
      }

      if (error_labels_omit) {
         bson_iter_t iter;

         if (!result->reply) {
            test_set_error (error, "%s", "expected error to omit labels, but got no error document");
            goto done;
         }

         BSON_FOREACH (error_labels_omit, iter)
         {
            const char *label;

            if (!BSON_ITER_HOLDS_UTF8 (&iter)) {
               test_set_error (
                  error, "expected UTF8 error label, got: %s", bson_type_to_string (bson_iter_type (&iter)));
               goto done;
            }

            label = bson_iter_utf8 (&iter, NULL);
            if (mongoc_error_has_label (result->reply, label)) {
               test_set_error (error, "expected error to omit label: %s, but got: %s", label, tmp_json (result->reply));
               goto done;
            }
         }
      }

      if (error_expect_result) {
         if (!result->value) {
            test_set_error (error, "%s", "expected error with result, but result unset");
            goto done;
         }

         /* Note: expectError.expectResult is not used for cursor-bearing
          * operations, so array_of_root_docs should always be false */
         BSON_ASSERT (!result->array_of_root_docs);

         if (!bson_match (error_expect_result, result->value, result->array_of_root_docs, error)) {
            test_diagnostics_error_info ("error.expectResult mismatch:\nExpected: %s\nActual: %s\n",
                                         bson_val_to_json (error_expect_result),
                                         bson_val_to_json (result->value));
            goto done;
         }
      }

      if (error_response) {
         if (!result->reply) {
            test_set_error (error, "%s", "expected error with a reply, but reply unset");
            goto done;
         }

         /* Write operations have the raw response wrapped in a errorReplies
          * array. Read operations return the raw response as the reply. */
         bson_t doc_to_match;
         bson_iter_t iter;

         if (bson_iter_init_find (&iter, result->reply, "errorReplies")) {
            bson_iter_t child;

            if (!BSON_ITER_HOLDS_ARRAY (&iter)) {
               test_set_error (error,
                               "expected errorReplies to be an array, but received %s",
                               bson_type_to_string (bson_iter_type (&iter)));
               goto done;
            }
            bson_iter_recurse (&iter, &child);
            bson_iter_next (&child);
            if (!BSON_ITER_HOLDS_DOCUMENT (&child)) {
               test_set_error (error,
                               "expected errorReplies.0 to be a document but received %s",
                               bson_type_to_string (bson_iter_type (&iter)));
               goto done;
            }
            bson_iter_bson (&child, &doc_to_match);
         } else {
            bson_copy_to (result->reply, &doc_to_match);
         }

         bson_val_t *val_to_match = bson_val_from_bson (&doc_to_match);

         if (!bson_match (error_response, val_to_match, result->array_of_root_docs, error)) {
            test_diagnostics_error_info ("error.errorResponse mismatch:\nExpected: %s\nActual: %s\n",
                                         bson_val_to_json (error_response),
                                         bson_as_relaxed_extended_json (result->reply, NULL));
            bson_val_destroy (val_to_match);
            bson_destroy (&doc_to_match);
            goto done;
         }
         bson_val_destroy (val_to_match);
         bson_destroy (&doc_to_match);
      }

      if (write_errors) {
         if (!result->write_errors) {
            test_set_error (error, "Expected writeErrors but got none");
            goto done;
         }

         // Ensure the numeric keys of the expected `writeErrors` exactly match
         // the actual `writeErrors`.
         {
            bson_t *expected = write_errors;
            bson_t *actual = result->write_errors;

            bson_iter_t expected_iter;
            bson_iter_init (&expected_iter, expected);
            while (bson_iter_next (&expected_iter)) {
               bson_val_t *expected_val = bson_val_from_iter (&expected_iter);
               bson_val_t *actual_val = NULL;
               const char *key = bson_iter_key (&expected_iter);
               bson_iter_t actual_iter;
               if (!bson_iter_init_find (&actual_iter, actual, key)) {
                  test_set_error (error,
                                  "error.writeErrors[%s] not found.\n"
                                  "Expected: %s\n"
                                  "Actual  : (not found)\n",
                                  key,
                                  bson_val_to_json (expected_val));
                  bson_val_destroy (actual_val);
                  bson_val_destroy (expected_val);
                  goto done;
               }
               actual_val = bson_val_from_iter (&actual_iter);

               if (!bson_match (expected_val, actual_val, false, error)) {
                  test_diagnostics_error_info ("error.writeErrors[%s] mismatch:\n"
                                               "Expected: %s\n"
                                               "Actual  : %s\n",
                                               key,
                                               bson_val_to_json (expected_val),
                                               bson_val_to_json (actual_val));
                  bson_val_destroy (actual_val);
                  bson_val_destroy (expected_val);
                  goto done;
               }
               bson_val_destroy (actual_val);
               bson_val_destroy (expected_val);
            }

            // Ensure no extra reported errors.
            bson_iter_t actual_iter;
            bson_iter_init (&actual_iter, actual);
            if (bson_iter_next (&actual_iter)) {
               bson_val_t *actual_val = bson_val_from_iter (&actual_iter);
               const char *key = bson_iter_key (&actual_iter);
               if (!bson_has_field (expected, key)) {
                  test_set_error (error,
                                  "error.writeErrors[%s] mismatch:\n"
                                  "Expected: (not found)\n"
                                  "Actual  : %s\n",
                                  key,
                                  bson_val_to_json (actual_val));
                  bson_val_destroy (actual_val);
                  goto done;
               }
               bson_val_destroy (actual_val);
            }
         }
      }

      if (write_concern_errors) {
         bson_val_t *expected_val = bson_val_from_array (write_concern_errors);
         bson_val_t *actual_val = bson_val_from_array (result->write_concern_errors);
         if (!bson_match (expected_val, actual_val, true /* array of root documents */, error)) {
            test_diagnostics_error_info ("error.writeConcernErrors mismatch:\n"
                                         "Expected: %s\n"
                                         "Actual  : %s\n",
                                         bson_val_to_json (expected_val),
                                         bson_val_to_json (actual_val));
            bson_val_destroy (actual_val);
            bson_val_destroy (expected_val);
            goto done;
         }
         bson_val_destroy (actual_val);
         bson_val_destroy (expected_val);
      }
   }

   ret = true;
done:
   bson_parser_destroy_with_parsed_fields (parser);
   return ret;
}

void
result_from_bulkwritereturn (result_t *result, mongoc_bulkwritereturn_t bwr)
{
   // Build up the result value as a BSON document.
   bson_t bwr_bson = BSON_INITIALIZER;
   if (bwr.res) {
      BSON_APPEND_INT32 (&bwr_bson, "insertedCount", mongoc_bulkwriteresult_insertedcount (bwr.res));
      BSON_APPEND_INT32 (&bwr_bson, "upsertedCount", mongoc_bulkwriteresult_upsertedcount (bwr.res));
      BSON_APPEND_INT32 (&bwr_bson, "matchedCount", mongoc_bulkwriteresult_matchedcount (bwr.res));
      BSON_APPEND_INT32 (&bwr_bson, "modifiedCount", mongoc_bulkwriteresult_modifiedcount (bwr.res));
      BSON_APPEND_INT32 (&bwr_bson, "deletedCount", mongoc_bulkwriteresult_deletedcount (bwr.res));
      const bson_t *ir = mongoc_bulkwriteresult_insertresults (bwr.res);
      if (ir) {
         BSON_APPEND_DOCUMENT (&bwr_bson, "insertResults", ir);
      }
      const bson_t *ur = mongoc_bulkwriteresult_updateresults (bwr.res);
      if (ur) {
         BSON_APPEND_DOCUMENT (&bwr_bson, "updateResults", ur);
      }
      const bson_t *dr = mongoc_bulkwriteresult_deleteresults (bwr.res);
      if (dr) {
         BSON_APPEND_DOCUMENT (&bwr_bson, "deleteResults", dr);
      }
   }

   bson_error_t error = {0};
   const bson_t *errorReply = NULL;
   // Include `errorLabels` and `errorReply` when initializing `result`.
   if (bwr.exc) {
      mongoc_bulkwriteexception_error (bwr.exc, &error);
      errorReply = mongoc_bulkwriteexception_errorreply (bwr.exc);
   }

   bson_val_t *bwr_val = bson_val_from_bson (&bwr_bson);
   result_from_val_and_reply (result, bwr_val, errorReply, &error);

   // Add `writeErrors` and `writeConcernErrors` after initializing.
   if (bwr.exc) {
      result->ok = false; // An error occurred.
      const bson_t *writeErrors = mongoc_bulkwriteexception_writeerrors (bwr.exc);
      bson_destroy (result->write_errors);
      result->write_errors = bson_copy (writeErrors);
      const bson_t *writeConcernErrors = mongoc_bulkwriteexception_writeconcernerrors (bwr.exc);
      bson_destroy (result->write_concern_errors);
      result->write_concern_errors = bson_copy (writeConcernErrors);
   }

   bson_destroy (&bwr_bson);
   bson_val_destroy (bwr_val);
}

static void
test_resultfrombulkwrite (void)
{
   bson_error_t error;
   result_t *result;
   bson_t *reply;
   bson_val_t *expect;
   bson_error_t empty = {0};

   result = result_new ();
   reply = tmp_bson ("{ 'nInserted': 0, 'nRemoved' : 1, 'nMatched' : 2, "
                     "'nModified' : 3, 'nUpserted' : 4 }");
   expect = bson_val_from_json ("{ 'insertedCount': 0, 'deletedCount': 1, 'matchedCount': 2, "
                                "'modifiedCount': 3, 'upsertedCount': 4}");
   result_from_bulk_write (result, reply, &empty);
   MONGOC_DEBUG ("rewritten to: %s", bson_val_to_json (result->value));
   if (!result_check (result, NULL, expect, NULL, &error)) {
      test_error ("result_check error: %s", error.message);
   }
   result_destroy (result);
   bson_val_destroy (expect);
}

void
test_result_install (TestSuite *suite)
{
   TestSuite_Add (suite, "/unified/result/resultfrombulkwrite", test_resultfrombulkwrite);
}
