text: #include <ctype.h>
  
  #include "aggregates.h"
  #include "arithmetic.h"
  #include "array.h"
  #include "bits-builtins.h"
  #include "cidr.h"
  #include "conversions.h"
  #include "encoding.h"
  #include "glob.h"
  #include "graphs.h"
  #include "json.h"
  #include "malloc.h"
  #include "memoize.h"
  #include "mpd.h"
  #include "numbers.h"
  #include "object.h"
  #include "regex.h"
  #include "set.h"
  #include "str.h"
  #include "strings.h"
  #include "test.h"
  #include "types.h"
  
  void reset_heap(void)
  {
      // This will leak memory!!
      // TODO: How should we safely reset it if we don't know the original starting ptr?
      opa_heap_ptr_set(opa_heap_top_get());
  }
  
  WASM_EXPORT(test_opa_malloc)
  void test_opa_malloc(void)
  {
      reset_heap();
  
      // NOTE(tsandall): These numbers are not particularly important. They're
      // sized to cause opa_malloc to call grow.memory. The tester initializes
      // memory with 2 pages so we allocate ~4 pages of memory here.
      const int N = 256;
      const int S = 1024;
  
      for(int i = 0; i < N; i++)
      {
          char *buf = opa_malloc(S);
  
          for(int x = 0; x < S; x++)
          {
              buf[x] = x % 255;
          }
      }
  }
  
  WASM_EXPORT(test_opa_malloc_min_size)
  void test_opa_malloc_min_size(void)
  {
      reset_heap();
  
      // Ensure that allocations less than the minimum size
      // are creating blocks large enough to be re-used by
      // the minimum size.
      void *too_small = opa_malloc(2);
      test("allocated min block", too_small != NULL);
  
      void *barrier = opa_malloc(0);
  
      opa_free(too_small);
  
      test("new free block", opa_heap_free_blocks() == 1);
  
      void *min_sized = opa_malloc(4);
      test("reused block", opa_heap_free_blocks() == 0);
      opa_free(min_sized);
      opa_free(barrier);
  }
  
  WASM_EXPORT(test_opa_malloc_split_threshold_small_block)
  void test_opa_malloc_split_threshold_small_block(void)
  {
      reset_heap();
  
      // Ensure that free blocks larger than the requested
      // allocation, but too small to leave a sufficiently
      // sized remainder, are left intact.
      size_t heap_block_size = 12;
      void *too_small = opa_malloc(2 * 128 + heap_block_size - 1);
      test("allocated too_small block", too_small != NULL);
  
      void *barrier = opa_malloc(256);
  
      opa_free(too_small);
  
      test("new small free block", opa_heap_free_blocks() == 1);
  
      // Expect the smaller allocation to use the bigger block
      // without splitting.
      void *new = opa_malloc(128);
      test("unable to split block", opa_heap_free_blocks() == 0);
      opa_free(new);
  }
  
  WASM_EXPORT(test_opa_malloc_split_threshold_big_block)
  void test_opa_malloc_split_threshold_big_block(void)
  {
      reset_heap();
  
      // Ensure that free blocks large enough to be split are split up
      // until they are too small: have space almost to allocate three
      // separate 128 blocks.
      size_t heap_block_size = 12;
      void *splittable = opa_malloc(3 * 128 + 2 * heap_block_size - 1);
      test("allocated splittable block", splittable != NULL);
  
      void *barrier = opa_malloc(128);
  
      opa_free(splittable);
      test("new large free block", opa_heap_free_blocks() == 1);
  
      // Expect to be able to get multiple blocks out of the new free one without
      // new allocations.
      unsigned int high = opa_heap_ptr_get();
  
      void *split1 = opa_malloc(128);
      void *split2 = opa_malloc(128);  // Too big to split remaining bytes, should take oversized block.
  
      test("heap ptr", high == opa_heap_ptr_get());
      test("remaining free blocks", opa_heap_free_blocks() == 0);
  
      opa_free(split1);
      opa_free(split2);
      opa_free(barrier);
  }
  
  WASM_EXPORT(test_opa_free)
  void test_opa_free(void)
  {
      reset_heap();
  
  
      // check the heap shrinks with a single malloc and free.
  
      size_t blocks = opa_heap_free_blocks();
      unsigned int base = opa_heap_ptr_get();
      opa_free(opa_malloc(0));
  
      test("heap ptr", base == opa_heap_ptr_get());
      test("free blocks", blocks == 0 && opa_heap_free_blocks() == 0);
  
      // check the double malloc, followed with frees in identical order
      // results in eventual heap shrinking.
  
      void *p1 = opa_malloc(0);
      void *p2 = opa_malloc(0);
      unsigned int high = opa_heap_ptr_get();
      test("free blocks", opa_heap_free_blocks() == 0);
  
      opa_free(p1);
      test("free blocks", opa_heap_free_blocks() == 1);
      test("heap ptr", high == opa_heap_ptr_get());
  
      opa_free(p2);
      test("free blocks", opa_heap_free_blocks() == 0);
      test("heap ptr", base == opa_heap_ptr_get());
  
      // check the double malloc, followed with frees in reverse order
      // results in gradual heap shrinking.
  
      p1 = opa_malloc(0);
      p2 = opa_malloc(0);
      high = opa_heap_ptr_get();
      test("free blocks", opa_heap_free_blocks() == 0);
  
      opa_free(p2);
      test("free blocks", opa_heap_free_blocks() == 0);
      test("heap ptr", high > opa_heap_ptr_get());
  
      opa_free(p1);
      test("free blocks", opa_heap_free_blocks() == 0);
      test("heap ptr", base == opa_heap_ptr_get());
  
      // check the free re-use (without splitting).
  
      p1 = opa_malloc(1);
      p2 = opa_malloc(1);
      high = opa_heap_ptr_get();
  
      opa_free(p1);
      test("free blocks", opa_heap_free_blocks() == 1);
  
      p1 = opa_malloc(1);
      test("free blocks", opa_heap_free_blocks() == 0);
      test("heap ptr", high == opa_heap_ptr_get());
  
      opa_free(p2);
      opa_free(p1);
      test("free blocks", opa_heap_free_blocks() == 0);
      test("heap ptr", base == opa_heap_ptr_get());
  
      // check the free re-use (with splitting).
  
      p1 = opa_malloc(512);
      p2 = opa_malloc(512);
      high = opa_heap_ptr_get();
  
      opa_free(p1);
      test("free blocks", opa_heap_free_blocks() == 1);
  
      p1 = opa_malloc(128);
      test("free blocks", opa_heap_free_blocks() == 1);
      test("heap ptr", high == opa_heap_ptr_get());
  
      opa_free(p2);
      test("free blocks", opa_heap_free_blocks() == 0);
  
      opa_free(p1);
      test("free blocks", opa_heap_free_blocks() == 0);
      test("heap ptr", base == opa_heap_ptr_get());
  }
  
  WASM_EXPORT(test_opa_memoize)
  void test_opa_memoize(void)
  {
      opa_memoize_init();
  
      opa_memoize_insert(100, opa_number_int(1));
      opa_memoize_insert(200, opa_number_int(2));
      opa_value *a = opa_memoize_get(100);
      opa_value *b = opa_memoize_get(200);
      opa_memoize_push();
      opa_value *c = opa_memoize_get(100);
      opa_value *d = opa_memoize_get(200);
      opa_memoize_insert(100, opa_number_int(3));
      opa_memoize_pop();
      opa_value *e = opa_memoize_get(100);
  
      opa_value *exp_a = opa_number_int(1);
      opa_value *exp_b = opa_number_int(2);
      opa_value *exp_c = NULL;
      opa_value *exp_d = NULL;
      opa_value *exp_e = opa_number_int(1);
  
      test("insert-a", opa_value_compare(a, exp_a) == 0);
      test("insert-b", opa_value_compare(b, exp_b) == 0);
      test("get-a-after-push", opa_value_compare(c, exp_c) == 0);
      test("get-b-after-push", opa_value_compare(d, exp_d) == 0);
      test("get-a-after-pop", opa_value_compare(e, exp_e) == 0);
  }
  
  // NOTE(sr): These tests are run in order. If they weren't, every test that
  // depends on mpd's state being initialized would have to call `opa_mpd_init`
  // first. When the Wasm module is used, the `Start` function (`_initialize`,
  // emitted from the Wasm compiler) takes care of that.
  WASM_EXPORT(test_opa_mpd)
  void test_opa_mpd(void)
  {
      // NOTE(sr): This call also initializes mpd_one, which is used under the
      // hood for `qadd_one`.
      opa_mpd_init();
      opa_value *zero = opa_number_int(0);
      opa_value *two = opa_bf_to_number(qadd_one(qadd_one(opa_number_to_bf(zero))));
      test("0+1+1 is 2", opa_value_compare(opa_number_int(2), two) == 0);
  }
  
  WASM_EXPORT(test_opa_strlen)
  void test_opa_strlen(void)
  {
      test("empty", opa_strlen("") == 0);
      test("non-empty", opa_strlen("1234") == 4);
  }
  
  WASM_EXPORT(test_opa_strncmp)
  void test_opa_strncmp(void)
  {
      test("empty", opa_strncmp("", "", 0) == 0);
      test("equal", opa_strncmp("1234", "1234", 4) == 0);
      test("less than", opa_strncmp("1234", "1243", 4) < 0);
      test("greater than", opa_strncmp("1243", "1234", 4) > 0);
  }
  
  WASM_EXPORT(test_opa_strcmp)
  void test_opa_strcmp(void)
  {
      test("empty", opa_strcmp("", "") == 0);
      test("equal", opa_strcmp("abcd", "abcd") == 0);
      test("less than", opa_strcmp("1234", "1243") < 0);
      test("greater than", opa_strcmp("1243", "1234") > 0);
      test("shorter", opa_strcmp("123", "1234") < 0);
      test("longer", opa_strcmp("1234", "123") > 0);
  }
  
  WASM_EXPORT(test_opa_itoa)
  void test_opa_itoa(void)
  {
      char buf[sizeof(long long)*8+1];
  
      test("itoa", opa_strcmp(opa_itoa(0, buf, 10), "0") == 0);
      test("itoa", opa_strcmp(opa_itoa(-128, buf, 10), "-128") == 0);
      test("itoa", opa_strcmp(opa_itoa(127, buf, 10), "127") == 0);
      test("itoa", opa_strcmp(opa_itoa(0x7FFFFFFFFFFFFFFF, buf, 10), "9223372036854775807") == 0);
      test("itoa", opa_strcmp(opa_itoa(0x8000000000000001, buf, 10), "-9223372036854775807") == 0);
      test("itoa", opa_strcmp(opa_itoa(0xFFFFFFFFFFFFFFFF, buf, 10), "-1") == 0);
  
      test("itoa/base2", opa_strcmp(opa_itoa(0, buf, 2), "0") == 0);
      test("itoa/base2", opa_strcmp(opa_itoa(-128, buf, 2), "-10000000") == 0);
      test("itoa/base2", opa_strcmp(opa_itoa(127, buf, 2), "1111111") == 0);
      test("itoa/base2", opa_strcmp(opa_itoa(0x7FFFFFFFFFFFFFFF, buf, 2), "111111111111111111111111111111111111111111111111111111111111111") == 0);
      test("itoa/base2", opa_strcmp(opa_itoa(0x8000000000000001, buf, 2), "-111111111111111111111111111111111111111111111111111111111111111") == 0);
      test("itoa/base2", opa_strcmp(opa_itoa(0xFFFFFFFFFFFFFFFF, buf, 2), "-1") == 0);
  
      test("itoa/base16", opa_strcmp(opa_itoa(0, buf, 16), "0") == 0);
      test("itoa/base16", opa_strcmp(opa_itoa(-128, buf, 16), "-80") == 0);
      test("itoa/base16", opa_strcmp(opa_itoa(127, buf, 16), "7f") == 0);
      test("itoa/base16", opa_strcmp(opa_itoa(0x7FFFFFFFFFFFFFFF, buf,16), "7fffffffffffffff") == 0);
      test("itoa/base16", opa_strcmp(opa_itoa(0x8000000000000001, buf, 16), "-7fffffffffffffff") == 0);
      test("itoa/base16", opa_strcmp(opa_itoa(0xFFFFFFFFFFFFFFFF, buf, 16), "-1") == 0);
  }
  
  
  int crunch_opa_atoi64(const char *str, long long exp, int exp_rc)
  {
      long long result;
      int rc;
  
      if ((rc = opa_atoi64(str, opa_strlen(str), &result)) != exp_rc)
      {
          return 0;
      }
  
      return exp_rc != 0 || result == exp;
  }
  
  WASM_EXPORT(test_opa_atoi64)
  void test_opa_atoi64(void)
  {
      test("integer", crunch_opa_atoi64("127", 127, 0));
      test("negative integer", crunch_opa_atoi64("-128", -128, 0));
      test("non integer", crunch_opa_atoi64("-128.3", 0, -2));
      test("empty", crunch_opa_atoi64("", 0, -1));
  }
  
  int crunch_opa_atof64(const char *str, double exp, int exp_rc)
  {
      double result;
      int rc;
  
      if ((rc = opa_atof64(str, opa_strlen(str), &result)) != exp_rc)
      {
          return 0;
      }
  
      return exp_rc != 0 || result == exp;
  }
  
  WASM_EXPORT(test_opa_atof64)
  void test_opa_atof64(void)
  {
      test("empty", crunch_opa_atof64("", 0, -1));
      test("bad integer", crunch_opa_atof64("1234-6", 0, -2));
      test("bad fraction", crunch_opa_atof64("1234.5-6", 0, -2));
      test("bad exponent", crunch_opa_atof64("1234.5e6-", 0, -2));
      test("bad exponent", crunch_opa_atof64("12345e6-", 0, -2));
      test("integer", crunch_opa_atof64("127", 127, 0));
      test("negative integer", crunch_opa_atof64("-128", -128, 0));
      test("fraction", crunch_opa_atof64("16.7", 16.7, 0));
      test("exponent", crunch_opa_atof64("6e7", 6e7, 0));
  }
  
  WASM_EXPORT(test_memchr)
  void test_memchr(void)
  {
      char s[] = { 1, 2, 2, 3 };
  
      test("memchr", memchr(s, 2, 1) == NULL);
      test("memchr", memchr(s, 2, sizeof(s)) == &s[1]);
      test("memchr", memchr(s, 4, sizeof(s)) == NULL);
  }
  
  WASM_EXPORT(test_memcmp)
  void test_memcmp(void)
  {
      char a[] = { 1, 2, 3, 4 }, b[] = { 1, 2, 3, 3 };
  
      test("memcmp", memcmp(a, b, 3) == 0);
      test("memcmp", memcmp(a, b, 4) == 1);
      test("memcmp", memcmp(b, a, 4) == -1);
  }
  
  WASM_EXPORT(test_memcpy)
  void test_memcpy(void)
  {
      char dest[] = { 1, 2, 3, 4 }, src[] = { 9, 8, 7 };
      char expected[] = { 9, 8, 3, 4 };
      memcpy(dest, src, 2);
  
      test("memcpy", memcmp(dest, expected, sizeof(expected)) == 0);
  }
  
  WASM_EXPORT(test_memset)
  void test_memset(void)
  {
      char s[] = { 9, 8, 7, 6 };
      char expected[] = { 1, 1, 1, 6 };
      memset(s, 1, 3);
  
      test("memset", memcmp(s, expected, sizeof(expected)) == 0);
  }
  
  int lex_crunch(const char *s)
  {
      opa_json_lex ctx;
      opa_json_lex_init(s, opa_strlen(s), &ctx);
      return opa_json_lex_read(&ctx);
  }
  
  WASM_EXPORT(test_opa_lex_tokens)
  void test_opa_lex_tokens(void)
  {
      test("empty", lex_crunch("") == OPA_JSON_TOKEN_EOF);
      test("space", lex_crunch(" ") == OPA_JSON_TOKEN_EOF);
      test("tab", lex_crunch("\t") == OPA_JSON_TOKEN_EOF);
      test("newline", lex_crunch("\n") == OPA_JSON_TOKEN_EOF);
      test("carriage return", lex_crunch("\r") == OPA_JSON_TOKEN_EOF);
      test("null", lex_crunch("null") == OPA_JSON_TOKEN_NULL);
      test("true", lex_crunch("true") == OPA_JSON_TOKEN_TRUE);
      test("false", lex_crunch("false") == OPA_JSON_TOKEN_FALSE);
  
      test("bad unicode", lex_crunch("\" \\uabcx \"") == OPA_JSON_TOKEN_ERROR); // not hex
      test("escape not closed", lex_crunch("\"a\\\"") == OPA_JSON_TOKEN_ERROR); // unmatched escape
      test("bad escape character", lex_crunch("\"\\Q\"") == OPA_JSON_TOKEN_ERROR); // invalid escape character Q
      test("object start", lex_crunch(" { ") == OPA_JSON_TOKEN_OBJECT_START);
      test("object end", lex_crunch(" } ") == OPA_JSON_TOKEN_OBJECT_END);
      test("array start", lex_crunch(" [ ") == OPA_JSON_TOKEN_ARRAY_START);
      test("array end", lex_crunch(" ] ") == OPA_JSON_TOKEN_ARRAY_END);
      test("element separator", lex_crunch(" , ") == OPA_JSON_TOKEN_COMMA);
      test("item separator", lex_crunch(" : ") == OPA_JSON_TOKEN_COLON);
  }
  
  int lex_buffer_crunch(const char *s, const char *exp, int token)
  {
      opa_json_lex ctx;
      opa_json_lex_init(s, opa_strlen(s), &ctx);
  
      if (opa_json_lex_read(&ctx) != token)
      {
          return -1;
      }
  
      size_t exp_len = opa_strlen(exp);
      size_t buf_len = ctx.buf_end - ctx.buf;
  
      if (exp_len != buf_len)
      {
          return -2;
      }
  
      if (opa_strncmp(ctx.buf, exp, buf_len) != 0)
      {
          return -3;
      }
  
      return 0;
  }
  
  #define test_lex_buffer(note, s, exp, token) test(note, (lex_buffer_crunch(s, exp, token) == 0))
  
  WASM_EXPORT(test_opa_lex_buffer)
  void test_opa_lex_buffer(void)
  {
      test_lex_buffer("zero", "0", "0", OPA_JSON_TOKEN_NUMBER);
      test_lex_buffer("signed zero", "-0", "-0", OPA_JSON_TOKEN_NUMBER);
      test_lex_buffer("integers", "1234567890", "1234567890", OPA_JSON_TOKEN_NUMBER);
      test_lex_buffer("signed integers", "-1234567890", "-1234567890", OPA_JSON_TOKEN_NUMBER);
      test_lex_buffer("floats", "0.1234567890", "0.1234567890", OPA_JSON_TOKEN_NUMBER);
      test_lex_buffer("signed floats", "-0.1234567890", "-0.1234567890", OPA_JSON_TOKEN_NUMBER);
      test_lex_buffer("exponents", "-0.1234567890e0", "-0.1234567890e0", OPA_JSON_TOKEN_NUMBER);
      test_lex_buffer("exponents", "-0.1234567890E+1000", "-0.1234567890E+1000", OPA_JSON_TOKEN_NUMBER);
      test_lex_buffer("exponents", "-0.1234567890E-1000", "-0.1234567890E-1000", OPA_JSON_TOKEN_NUMBER);
      test_lex_buffer("empty string", "\"\"", "", OPA_JSON_TOKEN_STRING);
      test_lex_buffer("escaped buffer", "\"a\\\"b\"", "a\\\"b", OPA_JSON_TOKEN_STRING_ESCAPED);
      test_lex_buffer("escaped quote", "\"\\\"\"", "\\\"", OPA_JSON_TOKEN_STRING_ESCAPED);
      test_lex_buffer("escaped reverse solidus", "\"\\\\\"", "\\\\", OPA_JSON_TOKEN_STRING_ESCAPED);
      test_lex_buffer("escaped solidus", "\"\\/\"", "\\/", OPA_JSON_TOKEN_STRING_ESCAPED);
      test_lex_buffer("escaped backspace", "\"\\b\"", "\\b", OPA_JSON_TOKEN_STRING_ESCAPED);
      test_lex_buffer("escaped feed forward", "\"\\f\"", "\\f", OPA_JSON_TOKEN_STRING_ESCAPED);
      test_lex_buffer("escaped line feed", "\"\\n\"", "\\n", OPA_JSON_TOKEN_STRING_ESCAPED);
      test_lex_buffer("escaped carriage return", "\"\\r\"", "\\r", OPA_JSON_TOKEN_STRING_ESCAPED);
      test_lex_buffer("escaped tab", "\"\\t\"", "\\t", OPA_JSON_TOKEN_STRING_ESCAPED);
      test_lex_buffer("plain", "\"abcdefg\"", "abcdefg", OPA_JSON_TOKEN_STRING);
  }
  
  WASM_EXPORT(test_opa_value_compare)
  void test_opa_value_compare(void)
  {
      test("none", opa_value_compare(NULL, NULL) == 0);
      test("none/some", opa_value_compare(NULL, opa_null()) < 0);
      test("some/none", opa_value_compare(opa_null(), NULL) > 0);
      test("null", opa_value_compare(opa_null(), opa_null()) == 0);
      test("null/boolean", opa_value_compare(opa_boolean(true), opa_null()) > 0);
      test("true/true", opa_value_compare(opa_boolean(true), opa_boolean(true)) == 0);
      test("true/false", opa_value_compare(opa_boolean(true), opa_boolean(false)) > 0);
      test("false/true", opa_value_compare(opa_boolean(false), opa_boolean(true)) < 0);
      test("false/false", opa_value_compare(opa_boolean(false), opa_boolean(false)) == 0);
      test("number/boolean", opa_value_compare(opa_number_int(100), opa_boolean(true)) > 0);
      test("integers", opa_value_compare(opa_number_int(100), opa_number_int(99)) > 0);
      test("integers", opa_value_compare(opa_number_int(100), opa_number_int(101)) < 0);
      test("integers", opa_value_compare(opa_number_int(100), opa_number_int(100)) == 0);
      test("integers", opa_value_compare(opa_number_int(-100), opa_number_int(100)) < 0);
      test("integers", opa_value_compare(opa_number_int(-100), opa_number_int(-101)) > 0);
      test("integer/float", opa_value_compare(opa_number_int(100), opa_number_float(100.1)) < 0);
      test("floats", opa_value_compare(opa_number_float(100.2), opa_number_float(100.1)) > 0);
      test("floats", opa_value_compare(opa_number_float(100.2), opa_number_float(100.3)) < 0);
      test("floats", opa_value_compare(opa_number_float(100.3), opa_number_float(100.3)) == 0);
      test("string/number", opa_value_compare(opa_string_terminated("foo"), opa_number_float(100)) > 0);
      test("strings", opa_value_compare(opa_string_terminated("foo"), opa_string_terminated("foo")) == 0);
      test("strings", opa_value_compare(opa_string_terminated("foo"), opa_string_terminated("bar")) > 0);
      test("strings", opa_value_compare(opa_string_terminated("bar"), opa_string_terminated("baz")) < 0);
      test("strings", opa_value_compare(opa_string_terminated("foobar"), opa_string_terminated("foo")) > 0);
      test("strings", opa_value_compare(opa_string_terminated("foo"), opa_string_terminated("foobar")) < 0);
  
      opa_array_t *arr1 = opa_cast_array(opa_array());
      opa_array_append(arr1, opa_number_int(1));
      opa_array_append(arr1, opa_number_int(2));
      opa_array_append(arr1, opa_number_int(3));
  
      opa_array_t *arr2 = opa_cast_array(opa_array());
      opa_array_append(arr2, opa_number_int(1));
      opa_array_append(arr2, opa_number_int(3));
      opa_array_append(arr2, opa_number_int(2));
  
      opa_array_t *arr3 = opa_cast_array(opa_array());
      opa_array_append(arr2, opa_number_int(1));
      opa_array_append(arr2, opa_number_int(3));
  
      opa_value *v1, *v2, *v3;
      v1 = &arr1->hdr;
      v2 = &arr2->hdr;
      v3 = &arr3->hdr;
  
      test("array/string", opa_value_compare(v1, opa_string_terminated("a")) > 0);
      test("arrays", opa_value_compare(v1, v1) == 0);
      test("arrays", opa_value_compare(v1, v2) < 0);
      test("arrays", opa_value_compare(v2, v1) > 0);
      test("arrays", opa_value_compare(v3, v2) < 0);
      test("arrays", opa_value_compare(v2, v3) > 0);
  
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(obj1, opa_string_terminated("b"), opa_number_int(2));
  
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(obj2, opa_string_terminated("b"), opa_number_int(3));
  
      opa_object_t *obj3 = opa_cast_object(opa_object());
      opa_object_insert(obj3, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(obj3, opa_string_terminated("c"), opa_number_int(3));
  
      opa_object_t *obj4 = opa_cast_object(opa_object());
      opa_object_insert(obj4, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(obj4, opa_string_terminated("b"), opa_number_int(2));
      opa_object_insert(obj4, opa_string_terminated("c"), opa_number_int(3));
  
      v1 = &obj1->hdr;
      v2 = &obj2->hdr;
      v3 = &obj3->hdr;
      opa_value *v4 = &obj4->hdr;
  
      test("object/array", opa_value_compare(v1, opa_array()) > 0);
      test("objects", opa_value_compare(v1, v1) == 0);
      test("objects", opa_value_compare(v1, v2) < 0);
      test("objects", opa_value_compare(v2, v3) < 0);
      test("objects", opa_value_compare(v4, v1) > 0);
      test("objects", opa_value_compare(v4, v2) < 0);
  
      opa_set_t *set1 = opa_cast_set(opa_set());
      opa_set_add(set1, opa_string_terminated("a"));
      opa_set_add(set1, opa_string_terminated("b"));
  
      opa_set_t *set2 = opa_cast_set(opa_set());
      opa_set_add(set2, opa_string_terminated("a"));
      opa_set_add(set2, opa_string_terminated("c"));
  
      opa_set_t *set3 = opa_cast_set(opa_set());
      opa_set_add(set3, opa_string_terminated("a"));
      opa_set_add(set3, opa_string_terminated("b"));
      opa_set_add(set3, opa_string_terminated("c"));
  
      v1 = &set1->hdr;
      v2 = &set2->hdr;
      v3 = &set3->hdr;
  
      test("set/object", opa_value_compare(v1, opa_object()) > 0);
      test("sets", opa_value_compare(v1, v1) == 0);
      test("sets", opa_value_compare(v1, v2) < 0);
      test("sets", opa_value_compare(v2, v3) > 0); // because c > b
      test("sets", opa_value_compare(v3, v1) > 0);
  }
  
  int parse_crunch(const char *s, opa_value *exp)
  {
      opa_value *ret = opa_json_parse(s, opa_strlen(s));
      if (ret == NULL)
      {
          return 0;
      }
      return opa_value_compare(exp, ret) == 0;
  }
  
  int value_parse_crunch(const char *s, opa_value *exp)
  {
      opa_value *ret = opa_value_parse(s, opa_strlen(s));
      if (ret == NULL)
      {
          return 0;
      }
      return opa_value_compare(exp, ret) == 0;
  }
  
  WASM_EXPORT(test_opa_json_parse_scalar)
  void test_opa_json_parse_scalar(void)
  {
      test("null", parse_crunch("null", opa_null()));
      test("true", parse_crunch("true", opa_boolean(true)));
      test("false", parse_crunch("false", opa_boolean(false)));
      test("strings", parse_crunch("\"hello\"", opa_string_terminated("hello")));
      test("strings: escaped quote", parse_crunch("\"a\\\"b\"", opa_string_terminated("a\"b")));
      test("strings: escaped reverse solidus", parse_crunch("\"a\\\\b\"", opa_string_terminated("a\\b")));
      test("strings: escaped solidus", parse_crunch("\"a\\/b\"", opa_string_terminated("a/b")));
      test("strings: escaped backspace", parse_crunch("\"a\\bb\"", opa_string_terminated("a\bb")));
      test("strings: escaped feed forward", parse_crunch("\"a\\fb\"", opa_string_terminated("a\fb")));
      test("strings: escaped line feed", parse_crunch("\"a\\nb\"", opa_string_terminated("a\nb")));
      test("strings: escaped carriage return", parse_crunch("\"a\\rb\"", opa_string_terminated("a\rb")));
      test("strings: escaped tab", parse_crunch("\"a\\tb\"", opa_string_terminated("a\tb")));
      test("strings: utf-8 2 bytes", parse_crunch("\"\xc2\xa2\"", opa_string_terminated("\xc2\xa2")));
      test("strings: utf-8 3 bytes", parse_crunch("\"\xe0\xb8\x81\"", opa_string_terminated("\xe0\xb8\x81")));
      test("strings: utf-8 3 bytes", parse_crunch("\"\xe2\x82\xac\"", opa_string_terminated("\xe2\x82\xac")));
      test("strings: utf-8 3 bytes", parse_crunch("\"\xed\x9e\xb0\"", opa_string_terminated("\xed\x9e\xb0")));
      test("strings: utf-8 3 bytes", parse_crunch("\"\xef\xa4\x80\"", opa_string_terminated("\xef\xa4\x80")));
      test("strings: utf-8 4 bytes", parse_crunch("\"\xf0\x90\x8d\x88\"", opa_string_terminated("\xf0\x90\x8d\x88")));
      test("strings: utf-8 4 bytes", parse_crunch("\"\xf3\xa0\x80\x81\"", opa_string_terminated("\xf3\xa0\x80\x81")));
      test("strings: utf-8 4 bytes", parse_crunch("\"\xf4\x80\x80\x80\"", opa_string_terminated("\xf4\x80\x80\x80")));
      test("strings: utf-16 no surrogate pair", parse_crunch("\" \\u20AC \"", opa_string_terminated(" \xe2\x82\xac ")));
      test("strings: utf-16 surrogate pair", parse_crunch("\" \\ud801\\udc37 \"", opa_string_terminated(" \xf0\x90\x90\xb7 ")));
      test("integers", parse_crunch("0", opa_number_int(0)));
      test("integers", parse_crunch("123456789", opa_number_int(123456789)));
      test("signed integers", parse_crunch("-0", opa_number_int(0)));
      test("signed integers", parse_crunch("-123456789", opa_number_int(-123456789)));
      test("floats", parse_crunch("16.7", opa_number_float(16.7)));
      test("signed floats", parse_crunch("-16.7", opa_number_float(-16.7)));
      test("exponents", parse_crunch("6e7", opa_number_float(6e7)));
  }
  
  WASM_EXPORT(test_opa_json_max_str_len)
  void test_opa_json_max_str_len(void)
  {
      test("max str len: a char", opa_json_max_string_len("a", 1) == 1);
      test("max str len: chars", opa_json_max_string_len("ab", 2) == 2);
      test("max str len: single char escape", opa_json_max_string_len("ab\nd", 4) == 4);
      test("max str len: 2 byte utf-8", opa_json_max_string_len("\xc2\xa2", 2) == 2);
      test("max str len: 3 byte utf-8", opa_json_max_string_len("\xe0\xb8\x81", 3) == 3);
      test("max str len: 4 byte utf-8", opa_json_max_string_len("\xf0\x90\x8d\x88", 4) == 4);
      test("max str len: utf-16 no surrogate pair", opa_json_max_string_len(" \\u20AC ", 8) == 6);
      test("max str len: utf-16 surrogate pair", opa_json_max_string_len(" \\ud801\\udc37 ", 14) == 6);
  }
  
  opa_array_t *fixture_array1(void)
  {
      opa_array_t *arr = opa_cast_array(opa_array());
      opa_array_append(arr, opa_number_int(1));
      opa_array_append(arr, opa_number_int(2));
      opa_array_append(arr, opa_number_int(3));
      opa_array_append(arr, opa_number_int(4));
      return arr;
  }
  
  opa_array_t *fixture_array2(void)
  {
      opa_array_t *arr1 = opa_cast_array(opa_array());
      opa_array_append(arr1, opa_number_int(1));
      opa_array_append(arr1, opa_number_int(2));
      opa_array_append(arr1, opa_number_int(3));
      opa_array_append(arr1, opa_number_int(4));
  
      opa_array_t *arr2 = opa_cast_array(opa_array());
      opa_array_append(arr2, opa_number_int(5));
      opa_array_append(arr2, opa_number_int(6));
      opa_array_append(arr2, opa_number_int(7));
      opa_array_append(arr2, opa_number_int(8));
  
      opa_array_t *arr = opa_cast_array(opa_array());
      opa_array_append(arr, &arr1->hdr);
      opa_array_append(arr, &arr2->hdr);
  
      return arr;
  }
  
  opa_object_t *fixture_object1(void)
  {
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(obj, opa_string_terminated("b"), opa_number_int(2));
      return obj;
  }
  
  opa_object_t *fixture_object2(void)
  {
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("c"), opa_number_int(1));
      opa_object_insert(obj1, opa_string_terminated("d"), opa_number_int(2));
  
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("e"), opa_number_int(3));
      opa_object_insert(obj2, opa_string_terminated("f"), opa_number_int(4));
  
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("a"), &obj1->hdr);
      opa_object_insert(obj, opa_string_terminated("b"), &obj2->hdr);
      return obj;
  }
  
  opa_set_t *fixture_set1(void)
  {
      opa_set_t *set = opa_cast_set(opa_set());
      opa_set_add(set, opa_string_terminated("a"));
      opa_set_add(set, opa_string_terminated("b"));
      return set;
  }
  
  WASM_EXPORT(test_opa_value_length)
  void test_opa_value_length(void)
  {
      opa_array_t *arr = fixture_array1();
      opa_object_t *obj = fixture_object1();
      opa_set_t *set = fixture_set1();
  
      test("arrays", opa_value_length(&arr->hdr) == 4);
      test("objects", opa_value_length(&obj->hdr) == 2);
      test("sets", opa_value_length(&set->hdr) == 2);
  }
  
  WASM_EXPORT(test_opa_value_get_array)
  void test_opa_value_get_array(void)
  {
      opa_array_t *arr = fixture_array1();
  
      for (int i = 0; i < 4; i++)
      {
          opa_value *result = opa_value_get(&arr->hdr, opa_number_int(i));
  
          if (result == NULL)
          {
              test_fatal("array get failed");
          }
  
          if (opa_value_compare(result, opa_number_int(i + 1)) != 0)
          {
              test_fatal("array get returned bad value");
          }
      }
  
      opa_value *result = opa_value_get(&arr->hdr, opa_string_terminated("foo"));
  
      if (result != NULL)
      {
          test_fatal("array get returned unexpected result");
      }
  
      result = opa_value_get(&arr->hdr, opa_number_float(3.14));
  
      if (result != NULL)
      {
          test_fatal("array get returned unexpected result");
      }
  
      result = opa_value_get(&arr->hdr, opa_number_int(-1));
  
      if (result != NULL)
      {
          test_fatal("array get returned unexpected result");
      }
  
      result = opa_value_get(&arr->hdr, opa_number_int(4));
  
      if (result != NULL)
      {
          test_fatal("array get returned unexpected result");
      }
  }
  
  WASM_EXPORT(test_opa_array_sort)
  void test_opa_array_sort(void)
  {
      opa_array_t *arr = opa_cast_array(opa_array());
  
      opa_array_append(arr, opa_number_int(4));
      opa_array_append(arr, opa_number_int(3));
      opa_array_append(arr, opa_number_int(2));
      opa_array_append(arr, opa_number_int(1));
  
      opa_array_sort(arr, opa_value_compare);
  
      // iterate through the array to verify both the indices and values.
  
      opa_value *res = opa_array();
      opa_value *exp = &fixture_array1()->hdr;
  
      for (opa_value *prev = NULL, *curr = NULL; (curr = opa_value_iter(&arr->hdr, prev)) != NULL; prev = curr)
      {
          opa_array_append(opa_cast_array(res), opa_value_get(&arr->hdr, curr));
      }
  
      if (opa_value_compare(res, exp) != 0)
      {
          test_fatal("array sort returned unexpected result");
      }
  }
  
  WASM_EXPORT(test_opa_value_get_object)
  void test_opa_value_get_object(void)
  {
      opa_object_t *obj = fixture_object1();
  
      const char *keys[2] = {
          "a",
          "b",
      };
  
      long long values[2] = {
          1,
          2,
      };
  
      for (int i = 0; i < sizeof(keys) / sizeof(const char *); i++)
      {
          opa_value *result = opa_value_get(&obj->hdr, opa_string_terminated(keys[i]));
  
          if (result == NULL)
          {
              test_fatal("object get failed");
          }
  
          if (opa_value_compare(result, opa_number_int(values[i])) != 0)
          {
              test_fatal("object get returned bad value");
          }
      }
  
      opa_value *result = opa_value_get(&obj->hdr, opa_string_terminated("non-existent"));
  
      if (result != NULL)
      {
          test_fatal("object get returned unexpected result");
      }
  }
  
  WASM_EXPORT(test_opa_json_parse_composites)
  void test_opa_json_parse_composites(void)
  {
  
      opa_value *empty_arr = opa_array();
  
      test("empty array", parse_crunch("[]", empty_arr));
      test("array", parse_crunch("[1,2,3,4]", &fixture_array1()->hdr));
      test("array nested", parse_crunch("[[1,2,3,4],[5,6,7,8]]", &fixture_array2()->hdr));
  
      opa_value *empty_obj = opa_object();
  
      test("empty object", parse_crunch("{}", empty_obj));
      test("object", parse_crunch("{\"a\": 1, \"b\": 2}", &fixture_object1()->hdr));
      test("object nested", parse_crunch("{\"a\": {\"c\": 1, \"d\": 2}, \"b\": {\"e\": 3, \"f\": 4}}", &fixture_object2()->hdr));
  }
  
  WASM_EXPORT(test_opa_value_parse)
  void test_opa_value_parse(void)
  {
      opa_set_t *set = opa_cast_set(opa_set());
      opa_set_add(set, opa_number_int(1));
  
      test("set of one", value_parse_crunch("{1}", &set->hdr));
      test("set of one - dupes", value_parse_crunch("{1,1}", &set->hdr));
  
      opa_set_add(set, opa_number_int(2));
      opa_set_add(set, opa_number_int(3));
  
      test("set multiple", value_parse_crunch("{1,2,3}", &set->hdr));
  
      opa_value *empty_set = opa_set();
  
      test("empty", value_parse_crunch("set()", empty_set));
      test("empty whitespace", value_parse_crunch("set(   )", empty_set));
  }
  
  WASM_EXPORT(test_opa_json_parse_memory_ownership)
  void test_opa_json_parse_memory_ownership(void)
  {
      char s[] = "[1,\"a\"]";
  
      opa_value *result = opa_json_parse(s, sizeof(s));
  
      opa_value *exp = opa_array();
      opa_array_t *arr = opa_cast_array(exp);
      opa_array_append(arr, opa_number_int(1));
      opa_array_append(arr, opa_string("a", 1));
  
      test("expected value", opa_value_compare(result, exp) == 0);
  
      for (int i = 0; i < sizeof(s); i++)
      {
          s[i] = 0;
      }
  
      test("expected value after overwriting buffer", opa_value_compare(result, exp) == 0);
  }
  
  WASM_EXPORT(test_opa_object_insert)
  void test_opa_object_insert(void)
  {
  
      opa_object_t *obj = opa_cast_object(opa_object());
  
      opa_object_insert(obj, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(obj, opa_string_terminated("b"), opa_number_int(2));
      opa_object_insert(obj, opa_string_terminated("a"), opa_number_int(3));
  
      opa_value *v1 = opa_value_get(&obj->hdr, opa_string_terminated("a"));
  
      if (opa_value_compare(v1, opa_number_int(3)) != 0)
      {
          test_fatal("object insert did not replace value")
      }
  
      opa_object_insert(obj, opa_string_terminated("b"), opa_number_int(4));
  
      opa_value *v2 = opa_value_get(&obj->hdr, opa_string_terminated("b"));
  
      if (opa_value_compare(v2, opa_number_int(4)) != 0)
      {
          test_fatal("object insert did not replace value")
      }
  }
  
  WASM_EXPORT(test_opa_object_growth)
  void test_opa_object_growth(void)
  {
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("a"), opa_string_terminated("1"));
      opa_object_insert(obj, opa_string_terminated("b"), opa_string_terminated("2"));
      opa_object_insert(obj, opa_string_terminated("c"), opa_string_terminated("3"));
      opa_object_insert(obj, opa_string_terminated("d"), opa_string_terminated("4"));
      opa_object_insert(obj, opa_string_terminated("e"), opa_string_terminated("5"));
      opa_object_insert(obj, opa_string_terminated("e"), opa_string_terminated("5'"));
  
      if (obj->len != 5)
      {
          test_fatal("object is missing key-value pairs")
      }
  
      if (obj->n != 8)
      {
          test_fatal("object capacity did double")
      }
  
      opa_object_insert(obj, opa_string_terminated("f"), opa_string_terminated("6"));
  
      if (obj->len != 6)
      {
          test_fatal("object is missing key-value pairs")
      }
  
      if (obj->n != 16)
      {
          test_fatal("object capacity did not double")
      }
  }
  
  WASM_EXPORT(test_opa_set_add_and_get)
  void test_opa_set_add_and_get(void)
  {
      opa_set_t *set = fixture_set1();
      opa_set_add(set, opa_string_terminated("a"));
  
      opa_set_t *cpy = fixture_set1();
  
      if (opa_value_compare(&set->hdr, &cpy->hdr) != 0)
      {
          test_fatal("set was modified by add with duplicate element");
      }
  
      opa_set_add(set, opa_string_terminated("c"));
  
      if (opa_value_compare(&set->hdr, &cpy->hdr) <= 0)
      {
          test_fatal("set should be greater than cpy")
      }
  
      if (opa_value_get(&set->hdr, opa_string_terminated("c")) == NULL)
      {
          test_fatal("set should contain string term c")
      }
  
      opa_set_t *order = opa_cast_set(opa_set());
      opa_set_add(order, opa_string_terminated("b"));
      opa_set_add(order, opa_string_terminated("c"));
      opa_set_add(order, opa_string_terminated("a"));
  
      if (opa_value_compare(&set->hdr, &order->hdr) != 0)
      {
          test_fatal("sets should be equal")
      }
  }
  
  WASM_EXPORT(test_opa_set_growth)
  void test_opa_set_growth(void)
  {
      opa_set_t *set = opa_cast_set(opa_set());
      opa_set_add(set, opa_string_terminated("a"));
      opa_set_add(set, opa_string_terminated("b"));
      opa_set_add(set, opa_string_terminated("c"));
      opa_set_add(set, opa_string_terminated("d"));
      opa_set_add(set, opa_string_terminated("e"));
      opa_set_add(set, opa_string_terminated("e"));
  
      if (set->len != 5)
      {
          test_fatal("set is missing elements")
      }
  
      if (set->n != 8)
      {
          test_fatal("set capacity did double")
      }
  
      opa_set_add(set, opa_string_terminated("f"));
  
      if (set->len != 6)
      {
          test_fatal("set is missing elements")
      }
  
      if (set->n != 16)
      {
          test_fatal("set capacity did not double")
      }
  }
  
  WASM_EXPORT(test_opa_value_iter_object)
  void test_opa_value_iter_object(void)
  {
      opa_object_t *obj = fixture_object1();
  
      opa_value *k1 = opa_value_iter(&obj->hdr, NULL);
      opa_value *k2 = opa_value_iter(&obj->hdr, k1);
      opa_value *k3 = opa_value_iter(&obj->hdr, k2);
  
      opa_value *exp1 = opa_string_terminated("b");
      opa_value *exp2 = opa_string_terminated("a");
      opa_value *exp3 = NULL;
  
      if (opa_value_compare(k1, exp1) != 0)
      {
          test_fatal("object iter start did not return expected value");
      }
  
      if (opa_value_compare(k2, exp2) != 0)
      {
          test_fatal("object iter second did not return expected value");
      }
  
      if (opa_value_compare(k3, exp3) != 0)
      {
          test_fatal("object iter third did not return expected value");
      }
  }
  
  WASM_EXPORT(test_opa_value_iter_array)
  void test_opa_value_iter_array(void)
  {
      opa_array_t *arr = opa_cast_array(opa_array());
  
      opa_array_append(arr, opa_number_int(1));
      opa_array_append(arr, opa_number_int(2));
  
      opa_value *k1 = opa_value_iter(&arr->hdr, NULL);
      opa_value *k2 = opa_value_iter(&arr->hdr, k1);
      opa_value *k3 = opa_value_iter(&arr->hdr, k2);
  
      opa_value *exp1 = opa_number_int(0);
      opa_value *exp2 = opa_number_int(1);
      opa_value *exp3 = NULL;
  
      if (opa_value_compare(k1, exp1) != 0)
      {
          test_fatal("array iter start did not return expected value");
      }
  
      if (opa_value_compare(k2, exp2) != 0)
      {
          test_fatal("array iter second did not return expected value");
      }
  
      if (opa_value_compare(k3, exp3) != 0)
      {
          test_fatal("array iter third did not return expected value");
      }
  }
  
  WASM_EXPORT(test_opa_value_iter_set)
  void test_opa_value_iter_set(void)
  {
      opa_set_t *set = opa_cast_set(opa_set());
  
      opa_set_add(set, opa_number_int(1));
      opa_set_add(set, opa_number_int(2));
  
      opa_value *v1 = opa_value_iter(&set->hdr, NULL);
      opa_value *v2 = opa_value_iter(&set->hdr, v1);
      opa_value *v3 = opa_value_iter(&set->hdr, v2);
  
      opa_value *exp1 = opa_number_int(1);
      opa_value *exp2 = opa_number_int(2);
      opa_value *exp3 = NULL;
  
      if (opa_value_compare(v1, exp1) != 0)
      {
          test_fatal("set iter did not return expected value");
      }
  
      if (opa_value_compare(v2, exp2) != 0)
      {
          test_fatal("set iter second did not return expected value");
      }
  
      if (opa_value_compare(v3, exp3) != 0)
      {
          test_fatal("set iter third did not return expected value");
      }
  }
  
  WASM_EXPORT(test_opa_value_merge_scalars)
  void test_opa_value_merge_scalars(void)
  {
      opa_value *result = opa_value_merge(opa_number_int(1), opa_string_terminated("foo"));
  
      if (result == NULL)
      {
          test_fatal("merge of two scalars failed");
      }
      else if (opa_value_compare(result, opa_number_int(1)) != 0)
      {
          test_fatal("scalar merge returned unexpected result");
      }
  }
  
  WASM_EXPORT(test_opa_value_merge_first_operand_null)
  void test_opa_value_merge_first_operand_null(void)
  {
      test_str_eq("second operand string returns string", "\"foo\"", opa_json_dump(opa_value_merge(NULL, opa_string_terminated("foo"))));
      test_str_eq("second operand object returns object", "{}", opa_json_dump(opa_value_merge(NULL, opa_object())));
      test_str_eq("second operand number returns number", "1", opa_json_dump(opa_value_merge(NULL, opa_number_int(1))));
      test_str_eq("second operand array returns array", "[]", opa_json_dump(opa_value_merge(NULL, opa_array())));
      test_str_eq("second operand set returns set", "set()", opa_value_dump(opa_value_merge(NULL, opa_set())));
      test_str_eq("second operand null returns null", "null", opa_json_dump(opa_value_merge(NULL, opa_null())));
  }
  
  WASM_EXPORT(test_opa_value_merge_simple)
  void test_opa_value_merge_simple(void)
  {
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_t *obj2 = opa_cast_object(opa_object());
  
      opa_object_insert(obj1, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(obj2, opa_string_terminated("b"), opa_number_int(2));
  
      opa_object_t *exp1 = opa_cast_object(opa_object());
      opa_object_insert(exp1, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(exp1, opa_string_terminated("b"), opa_number_int(2));
  
      opa_value *result = opa_value_merge(&obj1->hdr, &obj2->hdr);
  
      if (result == NULL)
      {
          test_fatal("object merge failed");
      }
      else if (opa_value_compare(result, &exp1->hdr) != 0)
      {
          test_fatal("object merge returned unexpected result");
      }
  }
  
  
  WASM_EXPORT(test_opa_value_merge_nested)
  void test_opa_value_merge_nested(void)
  {
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_t *obj1a = opa_cast_object(opa_object());
  
      opa_object_insert(obj1a, opa_string_terminated("b"), opa_number_int(1));
      opa_object_insert(obj1, opa_string_terminated("a"), &obj1a->hdr);
      opa_object_insert(obj1, opa_string_terminated("c"), opa_number_int(2));
  
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_t *obj2a = opa_cast_object(opa_object());
  
      opa_object_insert(obj2a, opa_string_terminated("d"), opa_number_int(3));
      opa_object_insert(obj2, opa_string_terminated("a"), &obj2a->hdr);
      opa_object_insert(obj2, opa_string_terminated("e"), opa_number_int(4));
  
      opa_object_t *exp1 = opa_cast_object(opa_object());
      opa_object_t *exp1a = opa_cast_object(opa_object());
  
      opa_object_insert(exp1a, opa_string_terminated("b"), opa_number_int(1));
      opa_object_insert(exp1a, opa_string_terminated("d"), opa_number_int(3));
      opa_object_insert(exp1, opa_string_terminated("a"), &exp1a->hdr);
      opa_object_insert(exp1, opa_string_terminated("c"), opa_number_int(2));
      opa_object_insert(exp1, opa_string_terminated("e"), opa_number_int(4));
  
      opa_value *result = opa_value_merge(&obj1->hdr, &obj2->hdr);
  
      if (result == NULL)
      {
          test_fatal("object merge failed");
      }
      else if (opa_value_compare(&exp1->hdr, result) != 0)
      {
          test_fatal("object merge returned unexpected result");
      }
  }
  
  WASM_EXPORT(test_opa_value_shallow_copy)
  void test_opa_value_shallow_copy(void)
  {
      // construct a value that has one of each type
      char str[] = "{\"a\": [1, true, null, 2.5]}";
      opa_value *obj = opa_json_parse(str, sizeof(str));
      opa_set_t *set = opa_cast_set(opa_set());
      opa_set_add(set, obj);
  
      opa_value *cpy = opa_value_shallow_copy(&set->hdr);
  
      if (opa_value_compare(cpy, &set->hdr) != 0)
      {
          test_fatal("expected original and shallow copy to be equal");
      }
  }
  
  WASM_EXPORT(test_opa_json_dump)
  void test_opa_json_dump(void)
  {
      test("null", opa_strcmp(opa_json_dump(opa_null()), "null") == 0);
      test("false", opa_strcmp(opa_json_dump(opa_boolean(false)), "false") == 0);
      test("true", opa_strcmp(opa_json_dump(opa_boolean(true)), "true") == 0);
      test("strings", opa_strcmp(opa_json_dump(opa_string_terminated("hello\"world")), "\"hello\\\"world\"") == 0);
      test("strings utf-8", opa_strcmp(opa_json_dump(opa_string_terminated("\xed\xba\xad")), "\"\xed\xba\xad\"") == 0);
      test("numbers", opa_strcmp(opa_json_dump(opa_number_int(127)), "127") == 0);
  
      // NOTE(tsandall): the string representation is lossy. We should store
      // user-supplied floating-point values as strings so that round-trip
      // operations are lossless. Computed values can be lossy for the time being.
      test("numbers/float", opa_strcmp(opa_json_dump(opa_number_float(12345.678)), "12345.7") == 0);
  
      // NOTE(tsandall): trailing zeros should be omitted but this appears to be an open issue: https://github.com/mpaland/printf/issues/55
      test("numbers/float", opa_strcmp(opa_json_dump(opa_number_float(10.5)), "10.5000") == 0);
  
      test("numbers/ref", opa_strcmp(opa_json_dump(opa_number_ref("127", 3)), "127") == 0);
  
      opa_value *arr = opa_array();
      test("arrays", opa_strcmp(opa_json_dump(arr), "[]") == 0);
  
      opa_array_append(opa_cast_array(arr), opa_string_terminated("hello"));
      test("arrays", opa_strcmp(opa_json_dump(arr), "[\"hello\"]") == 0);
  
      opa_array_append(opa_cast_array(arr), opa_string_terminated("world"));
      test("arrays", opa_strcmp(opa_json_dump(arr), "[\"hello\",\"world\"]") == 0);
  
      opa_value *set = opa_set();
      test("sets", opa_strcmp(opa_json_dump(set), "[]") == 0);
  
      opa_set_add(opa_cast_set(set), opa_string_terminated("hello"));
      test("sets", opa_strcmp(opa_json_dump(set), "[\"hello\"]") == 0);
  
      opa_set_add(opa_cast_set(set), opa_string_terminated("world"));
      test("sets", opa_strcmp(opa_json_dump(set), "[\"hello\",\"world\"]") == 0);
  
      opa_value *obj = opa_object();
      test("objects", opa_strcmp(opa_json_dump(obj), "{}") == 0);
  
      opa_object_insert(opa_cast_object(obj), opa_string_terminated("k1"), opa_string_terminated("v1"));
      test("objects", opa_strcmp(opa_json_dump(obj), "{\"k1\":\"v1\"}") == 0);
  
      opa_object_insert(opa_cast_object(obj), opa_string_terminated("k2"), opa_string_terminated("v2"));
      test("objects", opa_strcmp(opa_json_dump(obj), "{\"k1\":\"v1\",\"k2\":\"v2\"}") == 0);
  
      opa_value *terminators = opa_array();
      opa_array_append(opa_cast_array(terminators), opa_boolean(true));
      opa_array_append(opa_cast_array(terminators), opa_boolean(false));
      opa_array_append(opa_cast_array(terminators), opa_null());
  
      test("bool/null terminators", opa_strcmp(opa_json_dump(terminators), "[true,false,null]") == 0);
  
      opa_value *non_string_keys = opa_object();
      opa_array_t *arrk = opa_cast_array(opa_array());
      opa_array_append(arrk, opa_number_int(1));
      opa_object_insert(opa_cast_object(non_string_keys), &arrk->hdr, opa_number_int(1));
      test_str_eq("objects/non string keys", opa_json_dump(non_string_keys), "{\"[1]\":1}");
  }
  
  WASM_EXPORT(test_opa_value_dump)
  void test_opa_value_dump(void)
  {
      test("empty sets", opa_strcmp(opa_value_dump(opa_set()), "set()") == 0);
  
      opa_set_t *set = opa_cast_set(opa_set());
      opa_set_add(set, opa_number_int(1));
  
      test("sets of one", opa_strcmp(opa_value_dump(&set->hdr), "{1}") == 0);
  
      opa_set_add(set, opa_number_int(2));
      test("sets", opa_strcmp(opa_value_dump(&set->hdr), "{1,2}") == 0);
  
      opa_value *non_string_keys = opa_object();
      opa_array_t *arrk = opa_cast_array(opa_array());
      opa_array_append(arrk, opa_number_int(1));
      opa_object_insert(opa_cast_object(non_string_keys), &arrk->hdr, opa_number_int(1));
      test_str_eq("objects/non string keys", opa_value_dump(non_string_keys), "{[1]:1}");
  }
  
  WASM_EXPORT(test_arithmetic)
  void test_arithmetic(void)
  {
      long long i = 0;
  
      test("abs +1", opa_number_try_int(opa_cast_number(opa_arith_abs(opa_number_int(1))), &i) == 0 && i == 1);
      test("abs -1", opa_number_try_int(opa_cast_number(opa_arith_abs(opa_number_int(-1))), &i) == 0 && i == 1);
      test("abs 1.5 (float)", opa_number_as_float(opa_cast_number(opa_arith_abs(opa_number_float(1.5)))) == 1.5);
      test("abs -1.5 (float)", opa_number_as_float(opa_cast_number(opa_arith_abs(opa_number_float(-1.5)))) == 1.5);
      test("abs 1.5 (ref)", opa_number_as_float(opa_cast_number(opa_arith_abs(opa_number_ref("1.5", 3)))) == 1.5);
      test("abs -1.5 (ref)", opa_number_as_float(opa_cast_number(opa_arith_abs(opa_number_ref("-1.5", 4)))) == 1.5);
      test("round 1", opa_number_try_int(opa_cast_number(opa_arith_round(opa_number_int(1))), &i) == 0 && i == 1);
      test("round -1", opa_number_try_int(opa_cast_number(opa_arith_round(opa_number_int(-1))), &i) == 0 && i == -1);
      test("round 1.4 (float)", opa_number_as_float(opa_cast_number(opa_arith_round(opa_number_float(1.4)))) == 1);
      test("round -1.4 (float)", opa_number_as_float(opa_cast_number(opa_arith_round(opa_number_float(-1.4)))) == -1);
      test("round 1.5 (float)", opa_number_as_float(opa_cast_number(opa_arith_round(opa_number_float(1.5)))) == 2);
      test("round -1.5 (float)", opa_number_as_float(opa_cast_number(opa_arith_round(opa_number_float(-1.5)))) == -2);
      test("round 2.5 (float)", opa_number_as_float(opa_cast_number(opa_arith_round(opa_number_float(2.5)))) == 3);
      test("round -2.5 (float)", opa_number_as_float(opa_cast_number(opa_arith_round(opa_number_float(-2.5)))) == -3);
      test("ceil 1", opa_number_as_float(opa_cast_number(opa_arith_ceil(opa_number_int(1)))) == 1);
      test("ceil 1.01 (float)", opa_number_as_float(opa_cast_number(opa_arith_ceil(opa_number_float(1.01)))) == 2);
      test("ceil -1.99999 (float)", opa_number_as_float(opa_cast_number(opa_arith_ceil(opa_number_float(-1.99999)))) == -1);
      test("floor 1", opa_number_as_float(opa_cast_number(opa_arith_floor(opa_number_int(1)))) == 1);
      test("floor 1.01 (float)", opa_number_as_float(opa_cast_number(opa_arith_floor(opa_number_float(1.01)))) == 1);
      test("floor -1.99999 (float)", opa_number_as_float(opa_cast_number(opa_arith_floor(opa_number_float(-1.99999)))) == -2);
      test("plus 1+2", opa_number_as_float(opa_cast_number(opa_arith_plus(opa_number_float(1), opa_number_float(2)))) == 3);
      test("minus 3-2", opa_number_as_float(opa_cast_number(opa_arith_minus(opa_number_float(3), opa_number_float(2)))) == 1);
  
      opa_set_t *s1 = opa_cast_set(opa_set());
      opa_set_add(s1, opa_number_int(0));
      opa_set_add(s1, opa_number_int(1));
      opa_set_add(s1, opa_number_int(2));
  
      opa_set_t *s2 = opa_cast_set(opa_set());
      opa_set_add(s2, opa_number_int(0));
      opa_set_add(s2, opa_number_int(2));
  
      opa_set_t *s3 = opa_cast_set(opa_arith_minus(&s1->hdr, &s2->hdr));
      test("minus set", s3->len == 1 && opa_set_get(s3, opa_number_int(1)) != NULL);
      test("multiply 3*2", opa_number_as_float(opa_cast_number(opa_arith_multiply(opa_number_float(3), opa_number_float(2)))) == 6);
      test("divide 3/2", opa_number_as_float(opa_cast_number(opa_arith_divide(opa_number_float(3), opa_number_float(2)))) == 1.5);
      test("divide 3/0", opa_arith_divide(opa_number_float(3), opa_number_float(0)) == NULL);
      test("remainder 5 % 2", opa_number_as_float(opa_cast_number(opa_arith_rem(opa_number_float(5), opa_number_float(2)))) == 1);
      test("remainder 1.1 % 1", opa_arith_rem(opa_number_float(1.1), opa_number_float(1)) == NULL);
      test("remainder 1 % 1.1", opa_arith_rem(opa_number_float(1), opa_number_float(1.1)) == NULL);
      test("remainder 1 % 0", opa_arith_rem(opa_number_float(1), opa_number_float(0)) == NULL);
  }
  
  WASM_EXPORT(test_set_diff)
  void test_set_diff(void)
  {
      // test_arithmetic covers the diff.
  }
  
  WASM_EXPORT(test_set_intersection_union)
  void test_set_intersection_union(void)
  {
      opa_set_t *s1 = opa_cast_set(opa_set());
      opa_set_add(s1, opa_number_int(0));
      opa_set_add(s1, opa_number_int(1));
      opa_set_add(s1, opa_number_int(2));
  
      opa_set_t *s2 = opa_cast_set(opa_set());
      opa_set_add(s2, opa_number_int(0));
      opa_set_add(s2, opa_number_int(1));
  
      opa_set_t *r = opa_cast_set(opa_set_intersection(&s1->hdr, &s2->hdr));
      test("set/intersection", r->len == 2 && opa_set_get(r, opa_number_int(0)) != NULL && opa_set_get(r, opa_number_int(1)) != NULL);
  
      r = opa_cast_set(opa_set_union(&s1->hdr, &s2->hdr));
      test("set/union", r->len == 3 &&
           opa_set_get(r, opa_number_int(0)) != NULL &&
           opa_set_get(r, opa_number_int(1)) != NULL &&
           opa_set_get(r, opa_number_int(2)) != NULL);
  }
  
  
  WASM_EXPORT(test_sets_intersection_union)
  void test_sets_intersection_union(void)
  {
      opa_set_t *s1 = opa_cast_set(opa_set());
      opa_set_add(s1, opa_number_int(0));
      opa_set_add(s1, opa_number_int(1));
      opa_set_add(s1, opa_number_int(2));
  
      opa_set_t *s2 = opa_cast_set(opa_set());
      opa_set_add(s2, opa_number_int(0));
      opa_set_add(s2, opa_number_int(1));
  
      opa_set_t *s3 = opa_cast_set(opa_set());
      opa_set_add(s3, opa_number_int(0));
  
      opa_set_t *sets = opa_cast_set(opa_set());
      opa_set_add(sets, &s1->hdr);
      opa_set_add(sets, &s2->hdr);
      opa_set_add(sets, &s3->hdr);
  
      opa_set_t *r = opa_cast_set(opa_sets_intersection(&sets->hdr));
      test("sets/intersection", r->len == 1 && opa_set_get(r, opa_number_int(0)) != NULL);
  
      r = opa_cast_set(opa_sets_union(&sets->hdr));
      test("sets/union", r->len == 3 &&
           opa_set_get(r, opa_number_int(0)) != NULL &&
           opa_set_get(r, opa_number_int(1)) != NULL &&
           opa_set_get(r, opa_number_int(2)) != NULL);
  }
  
  WASM_EXPORT(test_array)
  void test_array(void)
  {
      opa_array_t *arr1 = opa_cast_array(opa_array());
      opa_array_append(arr1, opa_number_int(0));
      opa_array_append(arr1, opa_number_int(1));
  
      opa_array_t *arr2 = opa_cast_array(opa_array());
      opa_array_append(arr2, opa_number_int(2));
      opa_array_append(arr2, opa_number_int(3));
  
      opa_array_t *r = opa_cast_array(opa_array_concat(&arr1->hdr, &arr2->hdr));
  
      test("array_concat", r->len == 4 &&
           opa_value_compare(r->elems[0].v, opa_number_int(0)) == 0 &&
           opa_value_compare(r->elems[1].v, opa_number_int(1)) == 0 &&
           opa_value_compare(r->elems[2].v, opa_number_int(2)) == 0 &&
           opa_value_compare(r->elems[3].v, opa_number_int(3)) == 0);
  
      r = opa_cast_array(opa_array_slice(&r->hdr, opa_number_int(1), opa_number_int(3)));
  
      test("array_slice", r->len == 2 &&
           opa_value_compare(r->elems[0].v, opa_number_int(1)) == 0 &&
           opa_value_compare(r->elems[1].v, opa_number_int(2)) == 0);
  }
  
  WASM_EXPORT(test_types)
  void test_types(void)
  {
      test("is_number", opa_value_compare(opa_types_is_number(opa_number_int(0)), opa_boolean(true)) == 0);
      test("is_number", opa_types_is_number(opa_null()) == NULL);
      test("is_string", opa_value_compare(opa_types_is_string(opa_string("a", 1)), opa_boolean(true)) == 0);
      test("is_string", opa_types_is_string(opa_null()) == NULL);
      test("is_boolean", opa_value_compare(opa_types_is_boolean(opa_boolean(true)), opa_boolean(true)) == 0);
      test("is_boolean", opa_types_is_boolean(opa_null()) == NULL);
      test("is_array", opa_value_compare(opa_types_is_array(opa_array()), opa_boolean(true)) == 0);
      test("is_array", opa_types_is_array(opa_null()) == NULL);
      test("is_set", opa_value_compare(opa_types_is_set(opa_set()), opa_boolean(true)) == 0);
      test("is_set", opa_types_is_set(opa_null()) == NULL);
      test("is_object", opa_value_compare(opa_types_is_object(opa_object()), opa_boolean(true)) == 0);
      test("is_object", opa_types_is_object(opa_null()) == NULL);
      test("is_null", opa_value_compare(opa_types_is_null(opa_null()), opa_boolean(true)) == 0);
      test("is_null", opa_types_is_null(opa_number_int(0)) == NULL);
  
      test("name/null", opa_value_compare(opa_types_name(opa_null()), opa_string("null", 4)) == 0);
      test("name/boolean", opa_value_compare(opa_types_name(opa_boolean(true)), opa_string("boolean", 7)) == 0);
      test("name/number", opa_value_compare(opa_types_name(opa_number_int(0)), opa_string("number", 6)) == 0);
      test("name/string", opa_value_compare(opa_types_name(opa_string("a", 1)), opa_string("string", 6)) == 0);
      test("name/array", opa_value_compare(opa_types_name(opa_array()), opa_string("array", 5)) == 0);
      test("name/object", opa_value_compare(opa_types_name(opa_object()), opa_string("object", 6)) == 0);
      test("name/set", opa_value_compare(opa_types_name(opa_set()), opa_string("set", 3)) == 0);
  }
  
  static opa_value *number(const char *s)
  {
      size_t n = strlen(s);
      uint8_t sign = MPD_POS;
      size_t pos = 2;
  
      if (s[0] == '-')
      {
          sign = MPD_NEG;
          pos = 3;
      }
  
      int digits = n - pos;
      uint16_t rdata[digits];
  
      for (int i = 0; i < digits; i++)
      {
          int c = s[pos+i] & 0xff;
          if (isdigit(c))
          {
              c -= '0';
          } else if (isalpha(c)) {
              c -= isupper(c) ? 'A' - 10 : 'a' - 10;
          }
  
          rdata[digits - i - 1] = c;
      }
  
      uint32_t status = 0;
      mpd_t *r = mpd_qnew();
      mpd_qimport_u16(r, &rdata[0], digits, sign, 16, mpd_max_ctx(), &status);
      return opa_bf_to_number(r);
  }
  
  WASM_EXPORT(test_bits)
  void test_bits(void)
  {
      // tests from https://golang.org/src/math/big/int_test.go L1193
  
      struct and_or_xor_test
      {
          const char *x;
          const char *y;
          const char *and;
          const char *or;
          const char *xor;
      };
  
      struct and_or_xor_test tests1[] = {
          {"0x00", "0x00", "0x00", "0x00", "0x00"},
          {"0x00", "0x01", "0x00", "0x01", "0x01"},
          {"0x01", "0x00", "0x00", "0x01", "0x01"},
          {"-0x01", "0x00", "0x00", "-0x01", "-0x01"},
          {"-0xaf", "-0x50", "-0xf0", "-0x0f", "0xe1"},
          {"0x00", "-0x01", "0x00", "-0x01", "-0x01"},
          {"0x01", "0x01", "0x01", "0x01", "0x00"},
          {"-0x01", "-0x01", "-0x01", "-0x01", "0x00"},
          {"0x07", "0x08", "0x00", "0x0f", "0x0f"},
          {"0x05", "0x0f", "0x05", "0x0f", "0x0a"},
          {"0xff", "-0x0a", "0xf6", "-0x01", "-0xf7"},
          {"0x013ff6", "0x9a4e", "0x1a46", "0x01bffe", "0x01a5b8"},
          {"-0x013ff6", "0x9a4e", "0x800a", "-0x0125b2", "-0x01a5bc"},
          {"-0x013ff6", "-0x9a4e", "-0x01bffe", "-0x1a46", "0x01a5b8"},
          {
              "0x1000009dc6e3d9822cba04129bcbe3401",
              "0xb9bd7d543685789d57cb918e833af352559021483cdb05cc21fd",
              "0x1000001186210100001000009048c2001",
              "0xb9bd7d543685789d57cb918e8bfeff7fddb2ebe87dfbbdfe35fd",
              "0xb9bd7d543685789d57ca918e8ae69d6fcdb2eae87df2b97215fc",
              },
          {
              "0x1000009dc6e3d9822cba04129bcbe3401",
              "-0xb9bd7d543685789d57cb918e833af352559021483cdb05cc21fd",
              "0x8c40c2d8822caa04120b8321401",
              "-0xb9bd7d543685789d57ca918e82229142459020483cd2014001fd",
              "-0xb9bd7d543685789d57ca918e8ae69d6fcdb2eae87df2b97215fe",
          },
          {
              "-0x1000009dc6e3d9822cba04129bcbe3401",
              "-0xb9bd7d543685789d57cb918e833af352559021483cdb05cc21fd",
              "-0xb9bd7d543685789d57cb918e8bfeff7fddb2ebe87dfbbdfe35fd",
              "-0x1000001186210100001000009048c2001",
              "0xb9bd7d543685789d57ca918e8ae69d6fcdb2eae87df2b97215fc",
          },
      };
  
      for (int i = 0; i < sizeof(tests1)/sizeof(tests1[0]); i++) {
          test("and", opa_value_compare(number(tests1[i].and), opa_bits_and(number(tests1[i].x), number(tests1[i].y))) == 0);
          test("or", opa_value_compare(number(tests1[i].or), opa_bits_or(number(tests1[i].x), number(tests1[i].y))) == 0);
          test("xor", opa_value_compare(number(tests1[i].xor), opa_bits_xor(number(tests1[i].x), number(tests1[i].y))) == 0);
      }
  
      // tests from https://golang.org/src/math/big/int_test.go L1496
  
      struct negate_test
      {
          const char *input;
          const char *output;
      };
  
      struct negate_test tests2[] = {
          {"0", "-1"},
          {"1", "-2"},
          {"7", "-8"},
          {"0", "-1"},
          {"-81910", "81909"},
          {
              "298472983472983471903246121093472394872319615612417471234712061",
              "-298472983472983471903246121093472394872319615612417471234712062",
          },
       };
  
       for (int i = 0; i < sizeof(tests2)/sizeof(tests2[0]); i++) {
           test("negate", opa_value_compare(opa_number_ref(tests2[i].output, strlen(tests2[i].output)),
                                            opa_bits_negate(opa_number_ref(tests2[i].input, strlen(tests2[i].input)))) == 0);
           test("negate", opa_value_compare(opa_number_ref(tests2[i].input, strlen(tests2[i].input)),
                                            opa_bits_negate(opa_number_ref(tests2[i].output, strlen(tests2[i].output)))) == 0);
       }
  
       // tests from https://golang.org/src/math/big/int_test.go L883
  
       struct shift_test
       {
           const char *input;
           int shift;
           const char *output;
       };
  
       struct shift_test tests3[] = {
           {"0", 0, "0"},
           {"-0", 0, "0"},
           {"0", 1, "0"},
           {"0", 2, "0"},
           {"1", 0, "1"},
           {"1", 1, "0"},
           {"1", 2, "0"},
           {"2", 0, "2"},
           {"2", 1, "1"},
           {"-1", 0, "-1"},
           {"-1", 1, "-1"},
           {"-1", 10, "-1"},
           {"-100", 2, "-25"},
           {"-100", 3, "-13"},
           {"-100", 100, "-1"},
           {"4294967296", 0, "4294967296"},
           {"4294967296", 1, "2147483648"},
           {"4294967296", 2, "1073741824"},
           {"18446744073709551616", 0, "18446744073709551616"},
           {"18446744073709551616", 1, "9223372036854775808"},
           {"18446744073709551616", 2, "4611686018427387904"},
           {"18446744073709551616", 64, "1"},
           {"340282366920938463463374607431768211456", 64, "18446744073709551616"},
           {"340282366920938463463374607431768211456", 128, "1"},
       };
  
       for (int i = 0; i < sizeof(tests3)/sizeof(tests3[0]); i++) {
           test("right shift", opa_value_compare(opa_number_ref(tests3[i].output, strlen(tests3[i].output)),
                                                 opa_bits_shiftright(opa_number_ref(tests3[i].input, strlen(tests3[i].input)),
                                                                     opa_number_int(tests3[i].shift))) == 0);
       };
  
       // tests from https://golang.org/src/math/big/int_test.go L940
  
       struct shift_test tests4[] = {
           {"0", 0, "0"},
           {"0", 1, "0"},
           {"0", 2, "0"},
           {"1", 0, "1"},
           {"1", 1, "2"},
           {"1", 2, "4"},
           {"2", 0, "2"},
           {"2", 1, "4"},
           {"2", 2, "8"},
           {"-87", 1, "-174"},
           {"4294967296", 0, "4294967296"},
           {"4294967296", 1, "8589934592"},
           {"4294967296", 2, "17179869184"},
           {"18446744073709551616", 0, "18446744073709551616"},
           {"9223372036854775808", 1, "18446744073709551616"},
           {"4611686018427387904", 2, "18446744073709551616"},
           {"1", 64, "18446744073709551616"},
           {"18446744073709551616", 64, "340282366920938463463374607431768211456"},
           {"1", 128, "340282366920938463463374607431768211456"},
       };
  
       for (int i = 0; i < sizeof(tests4)/sizeof(tests4[0]); i++) {
           test("left shift", opa_value_compare(opa_number_ref(tests4[i].output, strlen(tests4[i].output)),
                                                opa_bits_shiftleft(opa_number_ref(tests4[i].input, strlen(tests4[i].input)),
                                                                   opa_number_int(tests4[i].shift))) == 0);
       };
  }
  
  WASM_EXPORT(test_aggregates)
  void test_aggregates(void)
  {
      opa_array_t *arr = opa_cast_array(opa_array());
      opa_array_append(arr, opa_number_int(2));
      opa_array_append(arr, opa_number_int(1));
      opa_array_append(arr, opa_number_int(4));
  
      opa_array_t *arr_sorted = opa_cast_array(opa_array());
      opa_array_append(arr_sorted, opa_number_int(1));
      opa_array_append(arr_sorted, opa_number_int(2));
      opa_array_append(arr_sorted, opa_number_int(4));
  
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("b"), opa_number_int(2));
      opa_object_insert(obj, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(obj, opa_string_terminated("c"), opa_number_int(4));
  
      opa_set_t *set = opa_cast_set(opa_set());
      opa_set_add(set, opa_number_int(2));
      opa_set_add(set, opa_number_int(1));
      opa_set_add(set, opa_number_int(4));
  
      test("count/string", opa_value_compare(opa_agg_count(opa_string("foo", 3)), opa_number_int(3)) == 0);
      test("count/unicode string", opa_value_compare(opa_agg_count(opa_string("\xC3\xA5\xC3\xA4\xC3\xB6", 6)), opa_number_int(3)) == 0);
      test("count/array", opa_value_compare(opa_agg_count(&arr->hdr), opa_number_int(3)) == 0);
      test("count/object", opa_value_compare(opa_agg_count(&obj->hdr), opa_number_int(3)) == 0);
      test("count/set", opa_value_compare(opa_agg_count(&set->hdr), opa_number_int(3)) == 0);
  
      test("sum/array", opa_value_compare(opa_agg_sum(&arr->hdr), opa_number_int(7)) == 0);
      test("sum/set", opa_value_compare(opa_agg_sum(&set->hdr), opa_number_int(7)) == 0);
  
      test("product/array", opa_value_compare(opa_agg_product(&arr->hdr), opa_number_int(8)) == 0);
      test("product/set", opa_value_compare(opa_agg_product(&set->hdr), opa_number_int(8)) == 0);
  
      test("max/array", opa_value_compare(opa_agg_max(&arr->hdr), opa_number_int(4)) == 0);
      test("max/set", opa_value_compare(opa_agg_max(&set->hdr), opa_number_int(4)) == 0);
  
      test("min/array", opa_value_compare(opa_agg_min(&arr->hdr), opa_number_int(1)) == 0);
      test("min/set", opa_value_compare(opa_agg_min(&set->hdr), opa_number_int(1)) == 0);
  
      test("sort/array", opa_value_compare(opa_agg_sort(&arr->hdr), &arr_sorted->hdr) == 0);
      test("sort/set", opa_value_compare(opa_agg_sort(&set->hdr), &arr_sorted->hdr) == 0);
  
      opa_array_t *arr_trues = opa_cast_array(opa_array());
      opa_array_append(arr_trues, opa_boolean(true));
      opa_array_append(arr_trues, opa_boolean(true));
  
      opa_array_t *arr_mixed = opa_cast_array(opa_array());
      opa_array_append(arr_mixed, opa_boolean(true));
      opa_array_append(arr_mixed, opa_boolean(false));
  
      opa_array_t *arr_falses = opa_cast_array(opa_array());
      opa_array_append(arr_falses, opa_boolean(false));
      opa_array_append(arr_falses, opa_boolean(false));
  
      test("all/array trues", opa_value_compare(opa_agg_all(&arr_trues->hdr), opa_boolean(true)) == 0);
      test("all/array mixed", opa_value_compare(opa_agg_all(&arr_mixed->hdr), opa_boolean(false)) == 0);
      test("all/array falses", opa_value_compare(opa_agg_all(&arr_falses->hdr), opa_boolean(false)) == 0);
      test("any/array trues", opa_value_compare(opa_agg_any(&arr_trues->hdr), opa_boolean(true)) == 0);
      test("any/array mixed", opa_value_compare(opa_agg_any(&arr_mixed->hdr), opa_boolean(true)) == 0);
      test("any/array falses", opa_value_compare(opa_agg_any(&arr_falses->hdr), opa_boolean(false)) == 0);
  
      opa_set_t *set_trues = opa_cast_set(opa_set());
      opa_set_add(set_trues, opa_boolean(true));
      opa_set_add(set_trues, opa_boolean(true));
  
      opa_set_t *set_mixed = opa_cast_set(opa_set());
      opa_set_add(set_mixed, opa_boolean(true));
      opa_set_add(set_mixed, opa_boolean(false));
  
      opa_set_t *set_falses = opa_cast_set(opa_set());
      opa_set_add(set_falses, opa_boolean(false));
      opa_set_add(set_falses, opa_boolean(false));
  
      test("all/set trues", opa_value_compare(opa_agg_all(&set_trues->hdr), opa_boolean(true)) == 0);
      test("all/set mixed", opa_value_compare(opa_agg_all(&set_mixed->hdr), opa_boolean(false)) == 0);
      test("all/set falses", opa_value_compare(opa_agg_all(&set_falses->hdr), opa_boolean(false)) == 0);
      test("any/set trues", opa_value_compare(opa_agg_any(&set_trues->hdr), opa_boolean(true)) == 0);
      test("any/set mixed", opa_value_compare(opa_agg_any(&set_mixed->hdr), opa_boolean(true)) == 0);
      test("any/set falses", opa_value_compare(opa_agg_any(&set_falses->hdr), opa_boolean(false)) == 0);
  }
  
  WASM_EXPORT(test_base64)
  void test_base64(void)
  {
      test("base64/is_valid", opa_value_compare(opa_base64_is_valid(opa_string_terminated("YWJjMTIzIT8kKiYoKSctPUB+")), opa_boolean(true)) == 0);
      test("base64/encode", opa_value_compare(opa_base64_encode(opa_string_terminated("abc123!?$*&()'-=@~")), opa_string_terminated("YWJjMTIzIT8kKiYoKSctPUB+")) == 0);
      test("base64/encode", opa_value_compare(opa_base64_encode(opa_string_terminated("This is a long string that should not be split to many lines")),
                                              opa_string_terminated("VGhpcyBpcyBhIGxvbmcgc3RyaW5nIHRoYXQgc2hvdWxkIG5vdCBiZSBzcGxpdCB0byBtYW55IGxpbmVz")) == 0);
      test("base64/decode", opa_value_compare(opa_base64_decode(opa_string_terminated("YWJjMTIzIT8kKiYoKSctPUB+")), opa_string_terminated("abc123!?$*&()'-=@~")) == 0);
      test("base64/decode", opa_value_compare(opa_base64_decode(opa_string_terminated("VGhpcyBpcyBhIGxvbmcgc3RyaW5nIHRoYXQgc2hvdWxkIG5vdCBiZSBzcGxpdCB0byBtYW55IGxpbmVz")),
                                              opa_string_terminated("This is a long string that should not be split to many lines")) == 0);
      test("base64/decode", opa_value_compare(opa_base64_decode(opa_string_terminated("VGhpcyBpcyBhIGxvbmcgc3RyaW5nIHRoYXQgY2FuIGJlIHBhcnNlZCBldmVuIGlmIHNwbGl0IHRv\nIG1hbnkgbGluZXM=")),
                                              opa_string_terminated("This is a long string that can be parsed even if split to many lines")) == 0);
      test("base64/url_encode", opa_value_compare(opa_base64_url_encode(opa_string_terminated("abc123!?$*&()'-=@~")), opa_string_terminated("YWJjMTIzIT8kKiYoKSctPUB-")) == 0);
      test("base64/url_decode", opa_value_compare(opa_base64_url_decode(opa_string_terminated("YWJjMTIzIT8kKiYoKSctPUB-")), opa_string_terminated("abc123!?$*&()'-=@~")) == 0);
  }
  
  WASM_EXPORT(test_json)
  void test_json(void)
  {
      test("json/marshal", opa_value_compare(opa_json_marshal(opa_string_terminated("string")), opa_string_terminated("\"string\"")) == 0);
      test("json/unmarshal", opa_value_compare(opa_json_unmarshal(opa_string_terminated("\"string\"")), opa_string_terminated("string")) == 0);
  }
  
  WASM_EXPORT(test_object)
  void test_object(void)
  {
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(obj, opa_string_terminated("b"), opa_number_int(2));
      opa_object_insert(obj, opa_string_terminated("c"), opa_number_int(3));
  
      test("object/get (key found)", opa_value_compare(builtin_object_get(&obj->hdr, opa_string_terminated("a"), opa_number_int(2)), opa_number_int(1)) == 0);
      test("object/get (string key not found)", opa_value_compare(builtin_object_get(&obj->hdr, opa_string_terminated("d"), opa_number_int(2)), opa_number_int(2)) == 0);
      test("object/get (integer key not found)", opa_value_compare(builtin_object_get(&obj->hdr, opa_number_int(1), opa_number_int(2)), opa_number_int(2)) == 0);
      test("object/get (boolean default value)", opa_value_compare(builtin_object_get(&obj->hdr, opa_number_int(1), opa_boolean(true)), opa_boolean(true)) == 0);
      test("object/get (non-object operand)", opa_value_compare(builtin_object_get(opa_string_terminated("a"), opa_number_int(1), opa_boolean(true)), NULL) == 0);
  
      opa_object_t *obj_keys = opa_cast_object(opa_object());
      opa_object_insert(obj_keys, opa_string_terminated("a"), opa_number_int(0));
      opa_object_insert(obj_keys, opa_string_terminated("c"), opa_number_int(0));
  
      opa_object_t *expected = opa_cast_object(opa_object());
      opa_object_insert(expected, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(expected, opa_string_terminated("c"), opa_number_int(3));
      test("object/filter (object keys)", opa_value_compare(builtin_object_filter(&obj->hdr, &obj_keys->hdr), &expected->hdr) == 0);
  
      opa_set_t *set_keys = opa_cast_set(opa_set());
      opa_set_add(set_keys, opa_string_terminated("a"));
      opa_set_add(set_keys, opa_string_terminated("c"));
      test("object/filter (set keys)", opa_value_compare(builtin_object_filter(&obj->hdr, &set_keys->hdr), &expected->hdr) == 0);
  
      opa_array_t *arr_keys = opa_cast_array(opa_array());
      opa_array_append(arr_keys, opa_string_terminated("a"));
      opa_array_append(arr_keys, opa_string_terminated("c"));
      test("object/filter (array keys)", opa_value_compare(builtin_object_filter(&obj->hdr, &arr_keys->hdr), &expected->hdr) == 0);
  }
  
  WASM_EXPORT(test_object_remove)
  void test_object_remove(void)
  {
      opa_object_t *o = opa_cast_object(opa_object());
      opa_object_insert(o, opa_string_terminated("c"), opa_number_int(3));
  
      // input -> {"a": 1, "b": {"c": 3}}
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(obj1, opa_string_terminated("b"), &o->hdr);
  
      opa_set_t *set_keys1 = opa_cast_set(opa_set());
      opa_set_add(set_keys1, opa_string_terminated("a"));
  
      opa_object_t *expected1 = opa_cast_object(opa_object());
      opa_object_insert(expected1, opa_string_terminated("b"), &o->hdr);
      test("object/remove (base)", opa_value_compare(builtin_object_remove(&obj1->hdr, &set_keys1->hdr), &expected1->hdr) == 0);
  
      // input -> {"a": 1, "b": {"c": 3}, "d": 4}
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(obj2, opa_string_terminated("b"), &o->hdr);
      opa_object_insert(obj2, opa_string_terminated("d"), opa_number_int(4));
  
      opa_set_t *set_keys2 = opa_cast_set(opa_set());
      opa_set_add(set_keys2, opa_string_terminated("b"));
      opa_set_add(set_keys2, opa_string_terminated("d"));
  
      opa_object_t *expected2 = opa_cast_object(opa_object());
      opa_object_insert(expected2, opa_string_terminated("a"), opa_number_int(1));
      test("object/remove (multiple keys set)", opa_value_compare(builtin_object_remove(&obj2->hdr, &set_keys2->hdr), &expected2->hdr) == 0);
  
      opa_array_t *arr_keys = opa_cast_array(opa_array());
      opa_array_append(arr_keys, opa_string_terminated("b"));
      opa_array_append(arr_keys, opa_string_terminated("d"));
      test("object/remove (multiple keys array)", opa_value_compare(builtin_object_remove(&obj2->hdr, &arr_keys->hdr), &expected2->hdr) == 0);
  
      opa_object_t *obj_keys = opa_cast_object(opa_object());
      opa_object_insert(obj_keys, opa_string_terminated("b"), opa_number_int(1));
      opa_object_insert(obj_keys, opa_string_terminated("d"), opa_string_terminated(""));
      test("object/remove (multiple keys object)", opa_value_compare(builtin_object_remove(&obj2->hdr, &obj_keys->hdr), &expected2->hdr) == 0);
  
      // input -> {"a": {"b": {"c": 3}}, "x": 123}
      opa_object_t *o2 = opa_cast_object(opa_object());
      opa_object_insert(o2, opa_string_terminated("b"), &o->hdr);
  
      opa_object_t *obj3 = opa_cast_object(opa_object());
      opa_object_insert(obj3, opa_string_terminated("a"), &o2->hdr);
      opa_object_insert(obj3, opa_string_terminated("x"), opa_number_int(123));
  
      opa_object_t *o_keys1 = opa_cast_object(opa_object());
      opa_object_insert(o_keys1, opa_string_terminated("foo"), opa_string_terminated("bar"));
  
      opa_object_t *o_keys2 = opa_cast_object(opa_object());
      opa_object_insert(o_keys2, opa_string_terminated("b"), &o_keys1->hdr);
  
      opa_object_t *obj_keys2 = opa_cast_object(opa_object());
      opa_object_insert(obj_keys2, opa_string_terminated("a"), &o_keys2->hdr);
  
      opa_object_t *expected3 = opa_cast_object(opa_object());
      opa_object_insert(expected3, opa_string_terminated("x"), opa_number_int(123));
      test("object/remove (multiple keys object nested)", opa_value_compare(builtin_object_remove(&obj3->hdr, &obj_keys2->hdr), &expected3->hdr) == 0);
  
      test("object/remove (empty object)", opa_value_compare(builtin_object_remove(opa_object(), &obj_keys2->hdr), opa_object()) == 0);
  
      test("object/remove (empty keys set)", opa_value_compare(builtin_object_remove(&obj3->hdr, opa_set()), &obj3->hdr) == 0);
  
      test("object/remove (empty keys array)", opa_value_compare(builtin_object_remove(&obj3->hdr, opa_array()), &obj3->hdr) == 0);
  
      test("object/remove (empty keys object)", opa_value_compare(builtin_object_remove(&obj3->hdr, opa_object()), &obj3->hdr) == 0);
  
      test("object/remove (non-object first operand)", opa_value_compare(builtin_object_remove(opa_string_terminated("a"),  opa_object()), NULL) == 0);
  
      opa_set_t *set_keys3 = opa_cast_set(opa_set());
      opa_set_add(set_keys3, opa_string_terminated("foo"));
      test("object/remove (key does not exist)", opa_value_compare(builtin_object_remove(&obj3->hdr, &set_keys3->hdr), &obj3->hdr) == 0);
  
      test("object/remove (second operand not object/set/array)", opa_value_compare(builtin_object_remove(&obj3->hdr, opa_string_terminated("a")), NULL) == 0);
  }
  
  WASM_EXPORT(test_object_union)
  void test_object_union(void)
  {
      test("object/union (both empty)", opa_value_compare(builtin_object_union(opa_object(), opa_object()), opa_object()) == 0);
  
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("a"), opa_number_int(1));
      test("object/union (left empty)", opa_value_compare(builtin_object_union(opa_object(), &obj1->hdr), &obj1->hdr) == 0);
  
      test("object/union (right empty)", opa_value_compare(builtin_object_union(&obj1->hdr, opa_object()), &obj1->hdr) == 0);
  
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("b"), opa_number_int(2));
  
      opa_object_t *expected1 = opa_cast_object(opa_object());
      opa_object_insert(expected1, opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(expected1, opa_string_terminated("b"), opa_number_int(2));
  
      test("object/union (base)", opa_value_compare(builtin_object_union(&obj1->hdr, &obj2->hdr), &expected1->hdr) == 0);
  
      opa_object_t *o = opa_cast_object(opa_object());
      opa_object_insert(o, opa_string_terminated("c"), opa_number_int(3));
      opa_object_t *o2 = opa_cast_object(opa_object());
      opa_object_insert(o2, opa_string_terminated("b"), &o->hdr);
      opa_object_t *obj3 = opa_cast_object(opa_object());
      opa_object_insert(obj3, opa_string_terminated("a"), &o2->hdr);
  
      opa_object_t *expected2 = opa_cast_object(opa_object());
      opa_object_insert(expected2, opa_string_terminated("a"),&o2->hdr);
      opa_object_insert(expected2, opa_string_terminated("b"), opa_number_int(2));
  
      test("object/union (nested)", opa_value_compare(builtin_object_union(&obj3->hdr, &obj2->hdr), &expected2->hdr) == 0);
      test("object/union (nested reverse)", opa_value_compare(builtin_object_union(&obj2->hdr, &obj3->hdr), &expected2->hdr) == 0);
  
      opa_object_t *obj4 = opa_cast_object(opa_object());
      opa_object_insert(obj4, opa_string_terminated("a"), opa_number_int(2));
  
      test("object/union (conflict simple)", opa_value_compare(builtin_object_union(&obj1->hdr, &obj4->hdr), &obj4->hdr) == 0);
  
      opa_object_insert(obj3, opa_string_terminated("d"), opa_number_int(7));
  
      test("object/union (conflict nested and extra field)", opa_value_compare(builtin_object_union(&obj1->hdr, &obj3->hdr), &obj3->hdr) == 0);
  
      // Operand 1 -> {"a": {"b": {"c": 1}}, "e": 1}
      opa_object_t *o3 = opa_cast_object(opa_object());
      opa_object_insert(o3, opa_string_terminated("c"), opa_number_int(1));
      opa_object_t *o4 = opa_cast_object(opa_object());
      opa_object_insert(o4, opa_string_terminated("b"), &o3->hdr);
      opa_object_t *obj5 = opa_cast_object(opa_object());
      opa_object_insert(obj5, opa_string_terminated("a"), &o4->hdr);
      opa_object_insert(obj5, opa_string_terminated("e"), opa_number_int(1));
  
      // Operand 2 -> {"a": {"b": "foo", "b1": "bar"}, "d": 7, "e": 17}
      opa_object_t *o5 = opa_cast_object(opa_object());
      opa_object_insert(o5, opa_string_terminated("b"), opa_string_terminated("foo"));
      opa_object_insert(o5, opa_string_terminated("b1"), opa_string_terminated("bar"));
      opa_object_t *obj6 = opa_cast_object(opa_object());
      opa_object_insert(obj6, opa_string_terminated("a"), &o5->hdr);
      opa_object_insert(obj6, opa_string_terminated("d"), opa_number_int(7));
      opa_object_insert(obj6, opa_string_terminated("e"), opa_number_int(17));
  
      // Expected -> {"a": {"b": "foo", "b1": "bar"}, "d": 7, "e": 17}
      opa_object_t *o6 = opa_cast_object(opa_object());
      opa_object_insert(o6, opa_string_terminated("b"), opa_string_terminated("foo"));
      opa_object_insert(o6, opa_string_terminated("b1"), opa_string_terminated("bar"));
      opa_object_t *expected3 = opa_cast_object(opa_object());
      opa_object_insert(expected3, opa_string_terminated("a"), &o6->hdr);
      opa_object_insert(expected3, opa_string_terminated("d"), opa_number_int(7));
      opa_object_insert(expected3, opa_string_terminated("e"), opa_number_int(17));
  
      test("object/union (conflict multiple-1)", opa_value_compare(builtin_object_union(&obj5->hdr, &obj6->hdr), &expected3->hdr) == 0);
  
      // Operand 1 -> {"a": {"b": {"c": 1, "d": 2}}, "e": 1}
      opa_object_t *o7 = opa_cast_object(opa_object());
      opa_object_insert(o7, opa_string_terminated("c"), opa_number_int(1));
      opa_object_insert(o7, opa_string_terminated("d"), opa_number_int(2));
      opa_object_t *o8 = opa_cast_object(opa_object());
      opa_object_insert(o8, opa_string_terminated("b"), &o7->hdr);
      opa_object_t *obj7 = opa_cast_object(opa_object());
      opa_object_insert(obj7, opa_string_terminated("a"), &o8->hdr);
      opa_object_insert(obj7, opa_string_terminated("e"), opa_number_int(1));
  
      // Operand 2 -> {"a": {"b": {"c": "foo"}, "b1": "bar"}, "d": 7, "e": 17}
      opa_object_t *o9 = opa_cast_object(opa_object());
      opa_object_insert(o9, opa_string_terminated("c"), opa_string_terminated("foo"));
      opa_object_t *o10 = opa_cast_object(opa_object());
      opa_object_insert(o10, opa_string_terminated("b"), &o9->hdr);
      opa_object_insert(o10, opa_string_terminated("b1"), opa_string_terminated("bar"));
      opa_object_t *obj8 = opa_cast_object(opa_object());
      opa_object_insert(obj8, opa_string_terminated("a"), &o10->hdr);
      opa_object_insert(obj8, opa_string_terminated("d"), opa_number_int(7));
      opa_object_insert(obj8, opa_string_terminated("e"), opa_number_int(17));
  
      // Expected -> {"a": {"b": {"c": "foo", "d": 2}, "b1": "bar"}, "d": 7, "e": 17}
      opa_object_t *o11 = opa_cast_object(opa_object());
      opa_object_insert(o11, opa_string_terminated("c"), opa_string_terminated("foo"));
      opa_object_insert(o11, opa_string_terminated("d"), opa_number_int(2));
      opa_object_t *o12 = opa_cast_object(opa_object());
      opa_object_insert(o12, opa_string_terminated("b"), &o11->hdr);
      opa_object_insert(o12, opa_string_terminated("b1"), opa_string_terminated("bar"));
      opa_object_t *expected4 = opa_cast_object(opa_object());
      opa_object_insert(expected4, opa_string_terminated("a"), &o12->hdr);
      opa_object_insert(expected4, opa_string_terminated("d"), opa_number_int(7));
      opa_object_insert(expected4, opa_string_terminated("e"), opa_number_int(17));
  
      test("object/union (conflict multiple-2)", opa_value_compare(builtin_object_union(&obj7->hdr, &obj8->hdr), &expected4->hdr) == 0);
  
      opa_object_t *obj9 = opa_cast_object(opa_object());
      opa_object_insert(obj9, opa_string_terminated("a"), opa_string_terminated("foo"));
      opa_object_insert(obj9, opa_string_terminated("b"), opa_string_terminated("bar"));
  
      opa_object_t *obj10 = opa_cast_object(opa_object());
      opa_object_insert(obj10, opa_string_terminated("a"), opa_string_terminated("baz"));
  
      opa_object_t *expected5 = opa_cast_object(opa_object());
      opa_object_insert(expected5, opa_string_terminated("a"), opa_string_terminated("baz"));
      opa_object_insert(expected5, opa_string_terminated("b"), opa_string_terminated("bar"));
  
      test("object/union (conflict multiple-3)", opa_value_compare(builtin_object_union(&obj9->hdr, &obj10->hdr), &expected5->hdr) == 0);
  
      test("object/union (non-object first operand)", opa_value_compare(builtin_object_union(opa_string_terminated("a"),  opa_object()), NULL) == 0);
  
      test("object/union (non-object second operand)", opa_value_compare(builtin_object_union(opa_object(), opa_string_terminated("a")), NULL) == 0);
  }
  
  opa_object_t *json_test_fixture_object1(void)
  {
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("c"), opa_number_int(7));
      opa_object_insert(obj1, opa_string_terminated("d"), opa_number_int(8));
  
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("b"), &obj1->hdr);
  
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("a"), &obj2->hdr);
      opa_object_insert(obj, opa_string_terminated("e"), opa_number_int(9));
      return obj;
  }
  
  opa_object_t *json_test_fixture_object2(void)
  {
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("c"), opa_number_int(7));
      opa_object_insert(obj1, opa_string_terminated("d"), opa_number_int(8));
  
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("b"), &obj1->hdr);
      opa_object_insert(obj2, opa_string_terminated("e"), opa_number_int(9));
  
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("a"), &obj2->hdr);
  
      return obj;
  }
  
  opa_object_t *json_test_fixture_object3(void)
  {
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("b"), opa_number_int(7));
  
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("a"), &obj1->hdr);
      opa_object_insert(obj, opa_string_terminated("c"), opa_number_int(1));
  
      return obj;
  }
  
  opa_object_t *json_test_fixture_object4(void)
  {
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("b"), opa_number_int(7));
      opa_object_insert(obj1, opa_string_terminated("c"), opa_number_int(8));
  
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("d"), opa_number_int(9));
  
      opa_array_t *arr1 = opa_cast_array(opa_array());
      opa_array_append(arr1, &obj1->hdr);
      opa_array_append(arr1, &obj2->hdr);
  
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("a"), &arr1->hdr);
  
      return obj;
  }
  
  opa_object_t *json_test_fixture_object5(void)
  {
      opa_array_t *arr1 = opa_cast_array(opa_array());
      opa_array_append(arr1, opa_string_terminated("b"));
      opa_array_append(arr1, opa_string_terminated("c"));
      opa_array_append(arr1, opa_string_terminated("d"));
  
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("1"), &arr1->hdr);
  
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("x"), opa_string_terminated("y"));
  
      opa_array_t *arr2 = opa_cast_array(opa_array());
      opa_array_append(arr2, &obj1->hdr);
      opa_array_append(arr2, &obj2->hdr);
  
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("a"), &arr2->hdr);
  
      return obj;
  }
  
  opa_object_t *json_test_fixture_object6(void)
  {
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("c"), opa_number_int(7));
      opa_object_insert(obj1, opa_string_terminated("d"), opa_number_int(8));
      opa_object_insert(obj1, opa_string_terminated("x"), opa_number_int(0));
  
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("b"), &obj1->hdr);
  
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("a"), &obj2->hdr);
      opa_object_insert(obj, opa_string_terminated("e"), opa_number_int(9));
      return obj;
  }
  
  opa_object_t *json_remove_get_exp_object1(void)
  {
      opa_array_t *arr1 = opa_cast_array(opa_array());
      opa_array_append(arr1, opa_string_terminated("b"));
      opa_array_append(arr1, opa_string_terminated("c"));
  
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("1"), &arr1->hdr);
  
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("x"), opa_string_terminated("y"));
  
      opa_array_t *arr2 = opa_cast_array(opa_array());
      opa_array_append(arr2, &obj1->hdr);
      opa_array_append(arr2, &obj2->hdr);
  
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("a"), &arr2->hdr);
  
      return obj;
  }
  
  opa_object_t *json_remove_get_exp_object2(void)
  {
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("x"), opa_number_int(0));
  
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("b"), &obj1->hdr);
  
      opa_object_t *obj = opa_cast_object(opa_object());
      opa_object_insert(obj, opa_string_terminated("a"), &obj2->hdr);
      opa_object_insert(obj, opa_string_terminated("e"), opa_number_int(9));
      return obj;
  }
  
  WASM_EXPORT(test_json_remove)
  void test_json_remove(void)
  {
      opa_object_t *obj1 = json_test_fixture_object1();
  
      opa_set_t *set_paths1 = opa_cast_set(opa_set());
      opa_set_add(set_paths1, opa_string_terminated("a/b/c"));
  
      opa_object_t *o1 = opa_cast_object(opa_object());
      opa_object_insert(o1, opa_string_terminated("d"), opa_number_int(8));
      opa_object_t *o2 = opa_cast_object(opa_object());
      opa_object_insert(o2, opa_string_terminated("b"), &o1->hdr);
  
      opa_object_t *expected1 = opa_cast_object(opa_object());
      opa_object_insert(expected1, opa_string_terminated("a"), &o2->hdr);
      opa_object_insert(expected1, opa_string_terminated("e"), opa_number_int(9));
  
      test("jsonremove/base", opa_value_compare(builtin_json_remove(&obj1->hdr, &set_paths1->hdr), &expected1->hdr) == 0);
  
      opa_set_add(set_paths1, opa_string_terminated("e"));
  
      opa_object_t *expected2 = opa_cast_object(opa_object());
      opa_object_insert(expected2, opa_string_terminated("a"), &o2->hdr);
  
      test("jsonremove/multiple roots", opa_value_compare(builtin_json_remove(&obj1->hdr, &set_paths1->hdr), &expected2->hdr) == 0);
  
      opa_array_t *array_paths1 = opa_cast_array(opa_array());
      opa_array_append(array_paths1, opa_string_terminated("a/b/c"));
      opa_array_append(array_paths1, opa_string_terminated("e"));
  
      test("jsonremove/multiple roots array", opa_value_compare(builtin_json_remove(&obj1->hdr, &array_paths1->hdr), &expected2->hdr) == 0);
  
      opa_object_t *obj2 = json_test_fixture_object2();
  
      opa_set_t *set_paths2 = opa_cast_set(opa_set());
      opa_set_add(set_paths2, opa_string_terminated("a/b/c"));
      opa_set_add(set_paths2, opa_string_terminated("a/e"));
  
      test("jsonremove/shared roots", opa_value_compare(builtin_json_remove(&obj2->hdr, &set_paths2->hdr), &expected2->hdr) == 0);
  
      opa_object_t *obj3 = json_test_fixture_object3();
      opa_set_t *set_paths3 = opa_cast_set(opa_set());
      opa_set_add(set_paths3, opa_string_terminated("a"));
      opa_set_add(set_paths3, opa_string_terminated("a/b"));
  
      opa_object_t *expected3 = opa_cast_object(opa_object());
      opa_object_insert(expected3, opa_string_terminated("c"), opa_number_int(1));
  
      test("jsonremove/conflict", opa_value_compare(builtin_json_remove(&obj3->hdr, &set_paths3->hdr), &expected3->hdr) == 0);
  
      opa_object_t *obj4 = opa_cast_object(opa_object());
      opa_object_insert(obj4, opa_string_terminated("a"), opa_number_int(7));
  
      test("jsonremove/empty list", opa_value_compare(builtin_json_remove(&obj4->hdr, opa_set()), &obj4->hdr) == 0);
  
      test("jsonremove/empty object", opa_value_compare(builtin_json_remove(opa_object(), &set_paths3->hdr), opa_object()) == 0);
  
      opa_object_t *obj5 = json_test_fixture_object1();
  
      opa_set_t *set_paths4 = opa_cast_set(opa_set());
      opa_set_add(set_paths4, opa_string_terminated("a"));
      opa_set_add(set_paths4, opa_string_terminated("e"));
  
      test("jsonremove/delete all", opa_value_compare(builtin_json_remove(&obj5->hdr, &set_paths4->hdr), opa_object()) == 0);
  
      opa_object_t *obj6 = json_test_fixture_object4();
  
      opa_set_t *set_paths5 = opa_cast_set(opa_set());
      opa_set_add(set_paths5, opa_string_terminated("a/0/b"));
      opa_set_add(set_paths5, opa_string_terminated("a/1"));
  
      opa_object_t *o3 = opa_cast_object(opa_object());
      opa_object_insert(o3, opa_string_terminated("c"), opa_number_int(8));
      opa_array_t *a3 = opa_cast_array(opa_array());
      opa_array_append(a3, &o3->hdr);
      opa_object_t *expected4 = opa_cast_object(opa_object());
      opa_object_insert(expected4, opa_string_terminated("a"), &a3->hdr);
  
      test("jsonremove/arrays", opa_value_compare(builtin_json_remove(&obj6->hdr, &set_paths5->hdr), &expected4->hdr) == 0);
  
      opa_object_t *obj7 = json_test_fixture_object5();
  
      opa_set_t *set_paths6 = opa_cast_set(opa_set());
      opa_set_add(set_paths6, opa_string_terminated("a/0/1/2"));
  
      opa_object_t *expected5 = json_remove_get_exp_object1();
  
      test("jsonremove/object with number keys", opa_value_compare(builtin_json_remove(&obj7->hdr, &set_paths6->hdr), &expected5->hdr) == 0);
  
      opa_object_t *obj8 = json_test_fixture_object1();
  
      opa_array_t *a4 = opa_cast_array(opa_array());
      opa_array_append(a4, opa_string_terminated("a"));
      opa_array_append(a4, opa_string_terminated("b"));
      opa_array_append(a4, opa_string_terminated("c"));
  
      opa_array_t *a5 = opa_cast_array(opa_array());
      opa_array_append(a5, opa_string_terminated("e"));
  
      opa_set_t *set_paths7 = opa_cast_set(opa_set());
      opa_set_add(set_paths7, &a4->hdr);
      opa_set_add(set_paths7, &a5->hdr);
  
      test("jsonremove/arrays of roots", opa_value_compare(builtin_json_remove(&obj8->hdr, &set_paths7->hdr), &expected2->hdr) == 0);
  
      opa_object_t *obj9 = json_test_fixture_object6();
  
      opa_set_t *set_paths8 = opa_cast_set(opa_set());
      opa_set_add(set_paths8, opa_string_terminated("a/b/d"));
      opa_set_add(set_paths8, &a4->hdr);
  
      opa_object_t *expected6 = json_remove_get_exp_object2();
  
      test("jsonremove/mixed root types", opa_value_compare(builtin_json_remove(&obj9->hdr, &set_paths8->hdr), &expected6->hdr) == 0);
  
      test("jsonremove/error (invalid first operand - string)", opa_value_compare(builtin_json_remove(opa_string_terminated("a"),  opa_set()), NULL) == 0);
  
      test("jsonremove/error (invalid first operand - number)", opa_value_compare(builtin_json_remove(opa_number_int(22),  opa_set()), NULL) == 0);
  
      test("jsonremove/error (invalid first operand - boolean)", opa_value_compare(builtin_json_remove(opa_boolean(true),  opa_set()), NULL) == 0);
  
      test("jsonremove/error (invalid first operand - array)", opa_value_compare(builtin_json_remove(opa_array(),  opa_set()), NULL) == 0);
  
      test("jsonremove/error (invalid second operand - string)", opa_value_compare(builtin_json_remove(opa_object(), opa_string_terminated("a")), NULL) == 0);
  
      test("jsonremove/error (invalid second operand - number)", opa_value_compare(builtin_json_remove(opa_object(), opa_number_int(22)), NULL) == 0);
  
      test("jsonremove/error (invalid second operand - boolean)", opa_value_compare(builtin_json_remove(opa_object(), opa_boolean(true)), NULL) == 0);
  
      test("jsonremove/error (invalid second operand - object)", opa_value_compare(builtin_json_remove(opa_object(), opa_object()), NULL) == 0);
  
      opa_set_t *set_paths9 = opa_cast_set(opa_set());
      opa_set_add(set_paths9, opa_number_int(1));
      opa_set_add(set_paths9, opa_string_terminated("a"));
  
      test("jsonremove/error invalid paths type set with numbers", opa_value_compare(builtin_json_remove(opa_object(), &set_paths9->hdr), NULL) == 0);
  
      opa_set_t *set_paths10 = opa_cast_set(opa_set());
      opa_set_add(set_paths10, opa_string_terminated("a"));
      opa_set_add(set_paths10, &obj9->hdr);
  
      test("jsonremove/error invalid paths type set with objects", opa_value_compare(builtin_json_remove(opa_object(), &set_paths10->hdr), NULL) == 0);
  
      opa_array_t *array_paths2 = opa_cast_array(opa_array());
      opa_array_append(array_paths2, opa_string_terminated("a"));
      opa_array_append(array_paths2, opa_number_int(1));
      opa_array_append(array_paths2, opa_number_int(2));
  
      test("jsonremove/error invalid paths type array with numbers", opa_value_compare(builtin_json_remove(opa_object(), &array_paths2->hdr), NULL) == 0);
  
      opa_array_t *array_paths3 = opa_cast_array(opa_array());
      opa_array_append(array_paths3, opa_string_terminated("a"));
      opa_array_append(array_paths3, opa_object());
  
      test("jsonremove/error invalid paths type array with objects", opa_value_compare(builtin_json_remove(opa_object(), &array_paths3->hdr), NULL) == 0);
  
      opa_set_t *set_paths11 = opa_cast_set(opa_set());
      opa_set_add(set_paths11, opa_string_terminated("a/b"));
      opa_set_add(set_paths11, opa_string_terminated("e"));
  
      opa_object_t *expected7 = opa_cast_object(opa_object());
      opa_object_insert(expected7, opa_string_terminated("a"), opa_object());
  
      test("jsonremove/delete last in object", opa_value_compare(builtin_json_remove(&obj5->hdr, &set_paths11->hdr), &expected7->hdr) == 0);
  }
  
  WASM_EXPORT(test_json_filter)
  void test_json_filter(void)
  {
      opa_object_t *obj1 = json_test_fixture_object1();
  
      opa_set_t *set_paths1 = opa_cast_set(opa_set());
      opa_set_add(set_paths1, opa_string_terminated("a/b/c"));
  
      opa_object_t *o1 = opa_cast_object(opa_object());
      opa_object_insert(o1, opa_string_terminated("c"), opa_number_int(7));
      opa_object_t *o2 = opa_cast_object(opa_object());
      opa_object_insert(o2, opa_string_terminated("b"), &o1->hdr);
  
      opa_object_t *expected1 = opa_cast_object(opa_object());
      opa_object_insert(expected1, opa_string_terminated("a"), &o2->hdr);
  
      test("jsonfilter/base", opa_value_compare(builtin_json_filter(&obj1->hdr, &set_paths1->hdr), &expected1->hdr) == 0);
  
      opa_set_add(set_paths1, opa_string_terminated("e"));
      opa_object_insert(expected1, opa_string_terminated("e"), opa_number_int(9));
  
      test("jsonfilter/multiple roots", opa_value_compare(builtin_json_filter(&obj1->hdr, &set_paths1->hdr), &expected1->hdr) == 0);
  
      opa_array_t *array_paths1 = opa_cast_array(opa_array());
      opa_array_append(array_paths1, opa_string_terminated("a/b/c"));
      opa_array_append(array_paths1, opa_string_terminated("e"));
  
      test("jsonfilter/multiple roots array", opa_value_compare(builtin_json_filter(&obj1->hdr, &array_paths1->hdr), &expected1->hdr) == 0);
  
      opa_object_t *obj2 = json_test_fixture_object2();
  
      opa_set_t *set_paths2 = opa_cast_set(opa_set());
      opa_set_add(set_paths2, opa_string_terminated("a/b/c"));
      opa_set_add(set_paths2, opa_string_terminated("a/e"));
  
      opa_object_t *o3 = opa_cast_object(opa_object());
      opa_object_insert(o3, opa_string_terminated("b"), &o1->hdr);
      opa_object_insert(o3, opa_string_terminated("e"), opa_number_int(9));
  
      opa_object_t *expected2 = opa_cast_object(opa_object());
      opa_object_insert(expected2, opa_string_terminated("a"), &o3->hdr);
  
      test("jsonfilter/shared roots", opa_value_compare(builtin_json_filter(&obj2->hdr, &set_paths2->hdr), &expected2->hdr) == 0);
  
      opa_object_t *o4 = opa_cast_object(opa_object());
      opa_object_insert(o4, opa_string_terminated("b"), opa_number_int(7));
  
      opa_object_t *obj3 = opa_cast_object(opa_object());
      opa_object_insert(obj3, opa_string_terminated("a"), &o4->hdr);
  
      opa_set_t *set_paths3 = opa_cast_set(opa_set());
      opa_set_add(set_paths3, opa_string_terminated("a"));
      opa_set_add(set_paths3, opa_string_terminated("a/b"));
  
      test("jsonfilter/conflict", opa_value_compare(builtin_json_filter(&obj3->hdr, &set_paths3->hdr), &obj3->hdr) == 0);
  
      test("jsonfilter/empty list", opa_value_compare(builtin_json_filter(&obj3->hdr, opa_set()), opa_object()) == 0);
  
      test("jsonfilter/empty object", opa_value_compare(builtin_json_filter(opa_object(), &set_paths3->hdr), opa_object()) == 0);
  
      opa_object_t *obj4 = json_test_fixture_object4();
      opa_set_t *set_paths4 = opa_cast_set(opa_set());
      opa_set_add(set_paths4, opa_string_terminated("a/0/b"));
      opa_set_add(set_paths4, opa_string_terminated("a/1"));
  
      opa_object_t *o5 = opa_cast_object(opa_object());
      opa_object_insert(o5, opa_string_terminated("b"), opa_number_int(7));
  
      opa_object_t *o6 = opa_cast_object(opa_object());
      opa_object_insert(o6, opa_string_terminated("d"), opa_number_int(9));
  
      opa_array_t *a1 = opa_cast_array(opa_array());
      opa_array_append(a1, &o5->hdr);
      opa_array_append(a1, &o6->hdr);
  
      opa_object_t *expected3 = opa_cast_object(opa_object());
      opa_object_insert(expected3, opa_string_terminated("a"), &a1->hdr);
  
      test("jsonfilter/arrays", opa_value_compare(builtin_json_filter(&obj4->hdr, &set_paths4->hdr), &expected3->hdr) == 0);
  
      opa_object_t *obj5 = json_test_fixture_object5();
      opa_set_t *set_paths5 = opa_cast_set(opa_set());
      opa_set_add(set_paths5, opa_string_terminated("a/0/1/2"));
  
      opa_array_t *a2 = opa_cast_array(opa_array());
      opa_array_append(a2, opa_string_terminated("d"));
  
      opa_object_t *o7 = opa_cast_object(opa_object());
      opa_object_insert(o7, opa_string_terminated("1"), &a2->hdr);
  
      opa_array_t *a3 = opa_cast_array(opa_array());
      opa_array_append(a3, &o7->hdr);
  
      opa_object_t *expected4 = opa_cast_object(opa_object());
      opa_object_insert(expected4, opa_string_terminated("a"), &a3->hdr);
  
      test("jsonfilter/object with number keys", opa_value_compare(builtin_json_filter(&obj5->hdr, &set_paths5->hdr), &expected4->hdr) == 0);
  
      opa_array_t *a4 = opa_cast_array(opa_array());
      opa_array_append(a4, opa_string_terminated("a"));
      opa_array_append(a4, opa_string_terminated("b"));
      opa_array_append(a4, opa_string_terminated("c"));
  
      opa_array_t *a5 = opa_cast_array(opa_array());
      opa_array_append(a5, opa_string_terminated("e"));
  
      opa_set_t *set_paths6 = opa_cast_set(opa_set());
      opa_set_add(set_paths6, &a4->hdr);
      opa_set_add(set_paths6, &a5->hdr);
  
      test("jsonfilter/arrays of roots", opa_value_compare(builtin_json_filter(&obj1->hdr, &set_paths6->hdr), &expected1->hdr) == 0);
  
      opa_object_t *obj6 = json_test_fixture_object6();
  
      opa_set_t *set_paths7 = opa_cast_set(opa_set());
      opa_set_add(set_paths7, opa_string_terminated("a/b/d"));
      opa_set_add(set_paths7, &a4->hdr);
  
      opa_object_t *o8 = opa_cast_object(opa_object());
      opa_object_insert(o8, opa_string_terminated("c"), opa_number_int(7));
      opa_object_insert(o8, opa_string_terminated("d"), opa_number_int(8));
  
      opa_object_t *o9 = opa_cast_object(opa_object());
      opa_object_insert(o9, opa_string_terminated("b"), &o8->hdr);
  
      opa_object_t *expected5 = opa_cast_object(opa_object());
      opa_object_insert(expected5, opa_string_terminated("a"), &o9->hdr);
  
      test("jsonfilter/mixed root types", opa_value_compare(builtin_json_filter(&obj6->hdr, &set_paths7->hdr), &expected5->hdr) == 0);
  
      test("jsonfilter/error (invalid first operand - string)", opa_value_compare(builtin_json_filter(opa_string_terminated("a"),  opa_set()), NULL) == 0);
  
      test("jsonfilter/error (invalid first operand - number)", opa_value_compare(builtin_json_filter(opa_number_int(22),  opa_set()), NULL) == 0);
  
      test("jsonfilter/error (invalid first operand - boolean)", opa_value_compare(builtin_json_filter(opa_boolean(true),  opa_set()), NULL) == 0);
  
      test("jsonfilter/error (invalid first operand - array)", opa_value_compare(builtin_json_filter(opa_array(),  opa_set()), NULL) == 0);
  
      test("jsonfilter/error (invalid second operand - string)", opa_value_compare(builtin_json_filter(opa_object(), opa_string_terminated("a")), NULL) == 0);
  
      test("jsonfilter/error (invalid second operand - number)", opa_value_compare(builtin_json_filter(opa_object(), opa_number_int(22)), NULL) == 0);
  
      test("jsonfilter/error (invalid second operand - boolean)", opa_value_compare(builtin_json_filter(opa_object(), opa_boolean(true)), NULL) == 0);
  
      test("jsonfilter/error (invalid second operand - object)", opa_value_compare(builtin_json_filter(opa_object(), opa_object()), NULL) == 0);
  }
  
  WASM_EXPORT(test_builtin_graph_reachable)
  void test_builtin_graph_reachable(void)
  {
  
      test("reachable/malformed graph", opa_value_compare(builtin_graph_reachable(opa_set(), opa_set()), opa_set()) == 0);
  
      opa_object_t *graph1 = opa_cast_object(opa_object());
      opa_set_t *initial1 = opa_cast_set(opa_set());
      opa_set_add(initial1, opa_string_terminated("a"));
  
      test("reachable/empty", opa_value_compare(builtin_graph_reachable(&graph1->hdr, &initial1->hdr), opa_set()) == 0);
  
      // graph -> {"a": {"b"}, "b": {"c"}, "c": {"a"}}
      opa_set_t *vertex1_1 = opa_cast_set(opa_set());
      opa_set_add(vertex1_1, opa_string_terminated("b"));
      opa_object_insert(graph1, opa_string_terminated("a"), &vertex1_1->hdr);
  
      opa_set_t *vertex2_1 = opa_cast_set(opa_set());
      opa_set_add(vertex2_1, opa_string_terminated("c"));
      opa_object_insert(graph1, opa_string_terminated("b"), &vertex2_1->hdr);
  
      opa_set_t *vertex3_1 = opa_cast_set(opa_set());
      opa_set_add(vertex3_1, opa_string_terminated("a"));
      opa_object_insert(graph1, opa_string_terminated("c"), &vertex3_1->hdr);
  
      opa_set_t *expected1 = opa_cast_set(opa_set());
      opa_set_add(expected1, opa_string_terminated("a"));
      opa_set_add(expected1, opa_string_terminated("b"));
      opa_set_add(expected1, opa_string_terminated("c"));
  
      test("reachable/cycle", opa_value_compare(builtin_graph_reachable(&graph1->hdr, &initial1->hdr), &expected1->hdr) == 0);
  
      // graph -> {"a": {"b", "c"}, "b": {"d"}, "c": {"d"}, "d": {}, "e": {"f"}, "f": {"e"}, "x": {"x"}}
      opa_object_t *graph2 = opa_cast_object(opa_object());
      opa_set_t *initial2 = opa_cast_set(opa_set());
      opa_set_add(initial2, opa_string_terminated("b"));
      opa_set_add(initial2, opa_string_terminated("e"));
  
      opa_set_t *vertex1_2 = opa_cast_set(opa_set());
      opa_set_add(vertex1_2, opa_string_terminated("b"));
      opa_set_add(vertex1_2, opa_string_terminated("c"));
      opa_object_insert(graph2, opa_string_terminated("a"), &vertex1_2->hdr);
  
      opa_set_t *vertex2_2 = opa_cast_set(opa_set());
      opa_set_add(vertex2_2, opa_string_terminated("d"));
      opa_object_insert(graph2, opa_string_terminated("b"), &vertex2_2->hdr);
      opa_object_insert(graph2, opa_string_terminated("c"), &vertex2_2->hdr);
  
      opa_object_insert(graph2, opa_string_terminated("d"), opa_set());
  
      opa_set_t *vertex3_2 = opa_cast_set(opa_set());
      opa_set_add(vertex3_2, opa_string_terminated("f"));
      opa_object_insert(graph2, opa_string_terminated("e"), &vertex3_2->hdr);
  
      opa_set_t *vertex4_2 = opa_cast_set(opa_set());
      opa_set_add(vertex4_2, opa_string_terminated("e"));
      opa_object_insert(graph2, opa_string_terminated("f"), &vertex4_2->hdr);
  
      opa_set_t *vertex5_2 = opa_cast_set(opa_set());
      opa_set_add(vertex5_2, opa_string_terminated("x"));
      opa_object_insert(graph2, opa_string_terminated("x"), &vertex5_2->hdr);
  
      opa_set_t *expected2 = opa_cast_set(opa_set());
      opa_set_add(expected2, opa_string_terminated("b"));
      opa_set_add(expected2, opa_string_terminated("d"));
      opa_set_add(expected2, opa_string_terminated("e"));
      opa_set_add(expected2, opa_string_terminated("f"));
  
      test("reachable/components", opa_value_compare(builtin_graph_reachable(&graph2->hdr, &initial2->hdr), &expected2->hdr) == 0);
  
      // graph -> {"a": ["b"], "b": ["c"], "c": ["a"]}
      opa_object_t *graph3 = opa_cast_object(opa_object());
      opa_array_t *initial3 = opa_cast_array(opa_array());
      opa_array_append(initial3, opa_string_terminated("a"));
  
      opa_array_t *vertex1_3 = opa_cast_array(opa_array());
      opa_array_append(vertex1_3, opa_string_terminated("b"));
      opa_object_insert(graph3, opa_string_terminated("a"), &vertex1_3->hdr);
  
      opa_array_t *vertex2_3 = opa_cast_array(opa_array());
      opa_array_append(vertex2_3, opa_string_terminated("c"));
      opa_object_insert(graph3, opa_string_terminated("b"), &vertex2_3->hdr);
  
      opa_array_t *vertex3_3 = opa_cast_array(opa_array());
      opa_array_append(vertex3_3, opa_string_terminated("a"));
      opa_object_insert(graph3, opa_string_terminated("c"), &vertex3_3->hdr);
  
      opa_set_t *expected3 = opa_cast_set(opa_set());
      opa_set_add(expected3, opa_string_terminated("a"));
      opa_set_add(expected3, opa_string_terminated("b"));
      opa_set_add(expected3, opa_string_terminated("c"));
  
      test("reachable/arrays", opa_value_compare(builtin_graph_reachable(&graph3->hdr, &initial3->hdr), &expected3->hdr) == 0);
  
      test("reachable/malformed initial nodes", opa_value_compare(builtin_graph_reachable(&graph3->hdr, opa_string_terminated("foo")), opa_set()) == 0);
  
      // graph -> {"a": null}
      opa_object_t *graph4 = opa_cast_object(opa_object());
      opa_object_insert(graph4, opa_string_terminated("a"), opa_null());
  
      opa_set_t *expected4 = opa_cast_set(opa_set());
      opa_set_add(expected4, opa_string_terminated("a"));
  
      test("reachable/null edge", opa_value_compare(builtin_graph_reachable(&graph4->hdr, &initial3->hdr), &expected4->hdr) == 0);
  }
  
  WASM_EXPORT(test_strings)
  void test_strings(void)
  {
      opa_value *join = opa_string_terminated("--");
  
      opa_array_t *arr0 = opa_cast_array(opa_array());
  
      opa_array_t *arr1 = opa_cast_array(opa_array());
      opa_array_append(arr1, opa_string_terminated("foo"));
  
      opa_array_t *arr2 = opa_cast_array(opa_array());
      opa_array_append(arr2, opa_string_terminated("foo"));
      opa_array_append(arr2, opa_string_terminated("bar"));
  
      opa_set_t *set0 = opa_cast_set(opa_set());
  
      opa_set_t *set1 = opa_cast_set(opa_set());
      opa_set_add(set1, opa_string_terminated("foo"));
  
      opa_set_t *set2 = opa_cast_set(opa_set());
      opa_set_add(set2, opa_string_terminated("foo"));
      opa_set_add(set2, opa_string_terminated("bar"));
  
      test("concat/array0", opa_value_compare(opa_strings_concat(join, &arr0->hdr), opa_string_terminated("")) == 0);
      test("concat/array1", opa_value_compare(opa_strings_concat(join, &arr1->hdr), opa_string_terminated("foo")) == 0);
      test("concat/array2", opa_value_compare(opa_strings_concat(join, &arr2->hdr), opa_string_terminated("foo--bar")) == 0);
      test("concat/set0", opa_value_compare(opa_strings_concat(join, &set0->hdr), opa_string_terminated("")) == 0);
      test("concat/set1", opa_value_compare(opa_strings_concat(join, &set1->hdr), opa_string_terminated("foo")) == 0);
      test("concat/set2", opa_value_compare(opa_strings_concat(join, &set2->hdr), opa_string_terminated("bar--foo")) == 0);
  
      test("contains/__", opa_value_compare(opa_strings_contains(opa_string_terminated(""), opa_string_terminated("")), opa_boolean(true)) == 0);
      test("contains/_a", opa_value_compare(opa_strings_contains(opa_string_terminated(""), opa_string_terminated("a")), opa_boolean(false)) == 0);
      test("contains/a_", opa_value_compare(opa_strings_contains(opa_string_terminated("a"), opa_string_terminated("")), opa_boolean(true)) == 0);
      test("contains/aa", opa_value_compare(opa_strings_contains(opa_string_terminated("a"), opa_string_terminated("a")), opa_boolean(true)) == 0);
      test("contains/ab", opa_value_compare(opa_strings_contains(opa_string_terminated("a"), opa_string_terminated("b")), opa_boolean(false)) == 0);
      test("contains/aab", opa_value_compare(opa_strings_contains(opa_string_terminated("a"), opa_string_terminated("ab")), opa_boolean(false)) == 0);
      test("contains/abb", opa_value_compare(opa_strings_contains(opa_string_terminated("ab"), opa_string_terminated("b")), opa_boolean(true)) == 0);
      test("contains/aab", opa_value_compare(opa_strings_contains(opa_string_terminated("aa"), opa_string_terminated("b")), opa_boolean(false)) == 0);
      test("contains/abab", opa_value_compare(opa_strings_contains(opa_string_terminated("ab"), opa_string_terminated("ab")), opa_boolean(true)) == 0);
      test("contains/abaa", opa_value_compare(opa_strings_contains(opa_string_terminated("ab"), opa_string_terminated("aa")), opa_boolean(false)) == 0);
      test("contains/abcbc", opa_value_compare(opa_strings_contains(opa_string_terminated("abc"), opa_string_terminated("bc")), opa_boolean(true)) == 0);
      test("contains/abcbd", opa_value_compare(opa_strings_contains(opa_string_terminated("abc"), opa_string_terminated("bd")), opa_boolean(false)) == 0);
  
      test("endswith/__", opa_value_compare(opa_strings_endswith(opa_string_terminated(""), opa_string_terminated("")), opa_boolean(true)) == 0);
      test("endswith/_a", opa_value_compare(opa_strings_endswith(opa_string_terminated(""), opa_string_terminated("a")), opa_boolean(false)) == 0);
      test("endswith/a_", opa_value_compare(opa_strings_endswith(opa_string_terminated("a"), opa_string_terminated("")), opa_boolean(true)) == 0);
      test("endswith/aa", opa_value_compare(opa_strings_endswith(opa_string_terminated("a"), opa_string_terminated("a")), opa_boolean(true)) == 0);
      test("endswith/ab", opa_value_compare(opa_strings_endswith(opa_string_terminated("a"), opa_string_terminated("b")), opa_boolean(false)) == 0);
      test("endswith/aab", opa_value_compare(opa_strings_endswith(opa_string_terminated("a"), opa_string_terminated("ab")), opa_boolean(false)) == 0);
      test("endswith/abb", opa_value_compare(opa_strings_endswith(opa_string_terminated("ab"), opa_string_terminated("b")), opa_boolean(true)) == 0);
      test("endswith/aab", opa_value_compare(opa_strings_endswith(opa_string_terminated("aa"), opa_string_terminated("b")), opa_boolean(false)) == 0);
      test("endswith/abab", opa_value_compare(opa_strings_endswith(opa_string_terminated("ab"), opa_string_terminated("ab")), opa_boolean(true)) == 0);
      test("endswith/abaa", opa_value_compare(opa_strings_endswith(opa_string_terminated("ab"), opa_string_terminated("aa")), opa_boolean(false)) == 0);
      test("endswith/abcbc", opa_value_compare(opa_strings_endswith(opa_string_terminated("abc"), opa_string_terminated("bc")), opa_boolean(true)) == 0);
      test("endswith/abcbd", opa_value_compare(opa_strings_endswith(opa_string_terminated("abc"), opa_string_terminated("bd")), opa_boolean(false)) == 0);
  
      test("format_int/2_0", opa_value_compare(opa_strings_format_int(opa_number_float(0), opa_number_int(2)), opa_string_terminated("0")) == 0);
      test("format_int/2_1", opa_value_compare(opa_strings_format_int(opa_number_float(1), opa_number_int(2)), opa_string_terminated("1")) == 0);
      test("format_int/2_-1", opa_value_compare(opa_strings_format_int(opa_number_float(-1), opa_number_int(2)), opa_string_terminated("-1")) == 0);
      test("format_int/2_2", opa_value_compare(opa_strings_format_int(opa_number_float(2), opa_number_int(2)), opa_string_terminated("10")) == 0);
      test("format_int/2_7", opa_value_compare(opa_strings_format_int(opa_number_float(7), opa_number_int(2)), opa_string_terminated("111")) == 0);
      test("format_int/8_0", opa_value_compare(opa_strings_format_int(opa_number_float(0), opa_number_int(8)), opa_string_terminated("0")) == 0);
      test("format_int/8_1", opa_value_compare(opa_strings_format_int(opa_number_float(1), opa_number_int(8)), opa_string_terminated("1")) == 0);
      test("format_int/8_-1", opa_value_compare(opa_strings_format_int(opa_number_float(-1), opa_number_int(8)), opa_string_terminated("-1")) == 0);
      test("format_int/8_8", opa_value_compare(opa_strings_format_int(opa_number_float(8), opa_number_int(8)), opa_string_terminated("10")) == 0);
      test("format_int/8_9", opa_value_compare(opa_strings_format_int(opa_number_float(9), opa_number_int(8)), opa_string_terminated("11")) == 0);
      test("format_int/10_0", opa_value_compare(opa_strings_format_int(opa_number_float(0), opa_number_int(10)), opa_string_terminated("0")) == 0);
      test("format_int/10_1", opa_value_compare(opa_strings_format_int(opa_number_float(1), opa_number_int(10)), opa_string_terminated("1")) == 0);
      test("format_int/10_-1", opa_value_compare(opa_strings_format_int(opa_number_float(-1), opa_number_int(10)), opa_string_terminated("-1")) == 0);
      test("format_int/10_10", opa_value_compare(opa_strings_format_int(opa_number_float(10), opa_number_int(10)), opa_string_terminated("10")) == 0);
      test("format_int/10_11", opa_value_compare(opa_strings_format_int(opa_number_float(11), opa_number_int(10)), opa_string_terminated("11")) == 0);
      test("format_int/16_0", opa_value_compare(opa_strings_format_int(opa_number_float(0), opa_number_int(16)), opa_string_terminated("0")) == 0);
      test("format_int/16_1", opa_value_compare(opa_strings_format_int(opa_number_float(1), opa_number_int(16)), opa_string_terminated("1")) == 0);
      test("format_int/16_-1", opa_value_compare(opa_strings_format_int(opa_number_float(-1), opa_number_int(16)), opa_string_terminated("-1")) == 0);
      test("format_int/16_15.5", opa_value_compare(opa_strings_format_int(opa_number_float(15.5), opa_number_int(16)), opa_string_terminated("f")) == 0);
      test("format_int/16_-15.5", opa_value_compare(opa_strings_format_int(opa_number_float(-15.5), opa_number_int(16)), opa_string_terminated("-f")) == 0);
      test("format_int/16_16", opa_value_compare(opa_strings_format_int(opa_number_float(16), opa_number_int(16)), opa_string_terminated("10")) == 0);
      test("format_int/16_31", opa_value_compare(opa_strings_format_int(opa_number_float(31), opa_number_int(16)), opa_string_terminated("1f")) == 0);
  
      test("indexof/__", opa_value_compare(opa_strings_indexof(opa_string_terminated(""), opa_string_terminated("")), opa_number_int(0)) == 0);
      test("indexof/_a", opa_value_compare(opa_strings_indexof(opa_string_terminated(""), opa_string_terminated("a")), opa_number_int(-1)) == 0);
      test("indexof/a_", opa_value_compare(opa_strings_indexof(opa_string_terminated("a"), opa_string_terminated("")), opa_number_int(0)) == 0);
      test("indexof/aa", opa_value_compare(opa_strings_indexof(opa_string_terminated("a"), opa_string_terminated("a")), opa_number_int(0)) == 0);
      test("indexof/ab", opa_value_compare(opa_strings_indexof(opa_string_terminated("a"), opa_string_terminated("b")), opa_number_int(-1)) == 0);
      test("indexof/aab", opa_value_compare(opa_strings_indexof(opa_string_terminated("a"), opa_string_terminated("ab")), opa_number_int(-1)) == 0);
      test("indexof/abb", opa_value_compare(opa_strings_indexof(opa_string_terminated("ab"), opa_string_terminated("b")), opa_number_int(1)) == 0);
      test("indexof/aab", opa_value_compare(opa_strings_indexof(opa_string_terminated("aa"), opa_string_terminated("b")), opa_number_int(-1)) == 0);
      test("indexof/abab", opa_value_compare(opa_strings_indexof(opa_string_terminated("ab"), opa_string_terminated("ab")), opa_number_int(0)) == 0);
      test("indexof/abaa", opa_value_compare(opa_strings_indexof(opa_string_terminated("ab"), opa_string_terminated("aa")), opa_number_int(-1)) == 0);
      test("indexof/abcbc", opa_value_compare(opa_strings_indexof(opa_string_terminated("abc"), opa_string_terminated("bc")), opa_number_int(1)) == 0);
      test("indexof/abcbd", opa_value_compare(opa_strings_indexof(opa_string_terminated("abc"), opa_string_terminated("bd")), opa_number_int(-1)) == 0);
      test("indexof/unicode", opa_value_compare(opa_strings_indexof(opa_string_terminated("\xC3\xA5\xC3\xA4\xC3\xB6"), opa_string_terminated("\xC3\xB6")), opa_number_int(2)) == 0);
  
      test("replace/___", opa_value_compare(opa_strings_replace(opa_string_terminated(""), opa_string_terminated(""), opa_string_terminated("")), opa_string_terminated("")) == 0);
      test("replace/_ab", opa_value_compare(opa_strings_replace(opa_string_terminated(""), opa_string_terminated("a"), opa_string_terminated("b")), opa_string_terminated("")) == 0);
      test("replace/aab", opa_value_compare(opa_strings_replace(opa_string_terminated("a"), opa_string_terminated("a"), opa_string_terminated("b")), opa_string_terminated("b")) == 0);
      test("replace/cab", opa_value_compare(opa_strings_replace(opa_string_terminated("c"), opa_string_terminated("a"), opa_string_terminated("b")), opa_string_terminated("c")) == 0);
      test("replace/aaab", opa_value_compare(opa_strings_replace(opa_string_terminated("aa"), opa_string_terminated("a"), opa_string_terminated("b")), opa_string_terminated("bb")) == 0);
      test("replace/acaab", opa_value_compare(opa_strings_replace(opa_string_terminated("aca"), opa_string_terminated("a"), opa_string_terminated("b")), opa_string_terminated("bcb")) == 0);
      test("replace/acaabd", opa_value_compare(opa_strings_replace(opa_string_terminated("aca"), opa_string_terminated("a"), opa_string_terminated("bd")), opa_string_terminated("bdcbd")) == 0);
      test("replace/cacab", opa_value_compare(opa_strings_replace(opa_string_terminated("cac"), opa_string_terminated("a"), opa_string_terminated("b")), opa_string_terminated("cbc")) == 0);
      test("replace/cacabd", opa_value_compare(opa_strings_replace(opa_string_terminated("cac"), opa_string_terminated("a"), opa_string_terminated("bd")), opa_string_terminated("cbdc")) == 0);
  
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("a"), opa_string_terminated("b"));
      opa_object_insert(obj2, opa_string_terminated("c"), opa_string_terminated("d"));
  
      test("replace_n/empty", opa_value_compare(opa_strings_replace_n(opa_object(), opa_string_terminated("a")), opa_string_terminated("a")) == 0);
      test("replace_n/two", opa_value_compare(opa_strings_replace_n(&obj2->hdr, opa_string_terminated("ac")), opa_string_terminated("bd")) == 0);
  
      opa_array_t *arr2b = opa_cast_array(opa_array());
      opa_array_append(arr2b, opa_string_terminated(""));
      opa_array_append(arr2b, opa_string_terminated("foo"));
  
      opa_array_t *arr2c = opa_cast_array(opa_array());
      opa_array_append(arr2c, opa_string_terminated("foo"));
      opa_array_append(arr2c, opa_string_terminated(""));
  
      opa_array_t *arr3 = opa_cast_array(opa_array());
      opa_array_append(arr3, opa_string_terminated("foo"));
      opa_array_append(arr3, opa_string_terminated("bar"));
      opa_array_append(arr3, opa_string_terminated("baz"));
  
      test("split/one", opa_value_compare(opa_strings_split(opa_string_terminated("foo"), opa_string_terminated(",")), &arr1->hdr) == 0);
      test("split/two_a", opa_value_compare(opa_strings_split(opa_string_terminated("foo,bar"), opa_string_terminated(",")), &arr2->hdr) == 0);
      test("split/two_b", opa_value_compare(opa_strings_split(opa_string_terminated(",,foo"), opa_string_terminated(",,")), &arr2b->hdr) == 0);
      test("split/two_c", opa_value_compare(opa_strings_split(opa_string_terminated("foo,,"), opa_string_terminated(",,")), &arr2c->hdr) == 0);
      test("split/three", opa_value_compare(opa_strings_split(opa_string_terminated("foo,,bar,,baz"), opa_string_terminated(",,")), &arr3->hdr) == 0);
  
      opa_array_t *arr4 = opa_cast_array(opa_array());
      opa_array_append(arr4, opa_string_terminated("f"));
      opa_array_append(arr4, opa_string_terminated("o"));
      opa_array_append(arr4, opa_string_terminated("o"));
  
      opa_array_t *arr5 = opa_cast_array(opa_array());
      opa_array_append(arr5, opa_string_terminated("f"));
      opa_array_append(arr5, opa_string_terminated("\xE2\x82\xAC")); // euro symbol
      opa_array_append(arr5, opa_string_terminated("o"));
  
      test("split/ascii", opa_value_compare(opa_strings_split(opa_string_terminated("foo"), opa_string_terminated("")), &arr4->hdr) == 0);
      test("split/utf8", opa_value_compare(opa_strings_split(opa_string_terminated("f\xE2\x82\xACo"), opa_string_terminated("")), &arr5->hdr) == 0);
  
      opa_array_t *arr6 = opa_cast_array(opa_array());
      opa_array_append(arr6, opa_string_terminated(""));
      test("split/empty", opa_value_compare(opa_strings_split(opa_string_terminated(""), opa_string_terminated(",")), &arr6->hdr) == 0);
  
      test("startswith/__", opa_value_compare(opa_strings_startswith(opa_string_terminated(""), opa_string_terminated("")), opa_boolean(true)) == 0);
      test("startswith/_a", opa_value_compare(opa_strings_startswith(opa_string_terminated(""), opa_string_terminated("a")), opa_boolean(false)) == 0);
      test("startswith/a_", opa_value_compare(opa_strings_startswith(opa_string_terminated("a"), opa_string_terminated("")), opa_boolean(true)) == 0);
      test("startswith/aa", opa_value_compare(opa_strings_startswith(opa_string_terminated("a"), opa_string_terminated("a")), opa_boolean(true)) == 0);
      test("startswith/ab", opa_value_compare(opa_strings_startswith(opa_string_terminated("a"), opa_string_terminated("b")), opa_boolean(false)) == 0);
      test("startswith/aab", opa_value_compare(opa_strings_startswith(opa_string_terminated("a"), opa_string_terminated("ab")), opa_boolean(false)) == 0);
      test("startswith/aba", opa_value_compare(opa_strings_startswith(opa_string_terminated("ab"), opa_string_terminated("a")), opa_boolean(true)) == 0);
      test("startswith/aab", opa_value_compare(opa_strings_startswith(opa_string_terminated("aa"), opa_string_terminated("b")), opa_boolean(false)) == 0);
      test("startswith/abab", opa_value_compare(opa_strings_startswith(opa_string_terminated("ab"), opa_string_terminated("ab")), opa_boolean(true)) == 0);
      test("startswith/abaa", opa_value_compare(opa_strings_startswith(opa_string_terminated("ab"), opa_string_terminated("aa")), opa_boolean(false)) == 0);
      test("startswith/abcab", opa_value_compare(opa_strings_startswith(opa_string_terminated("abc"), opa_string_terminated("ab")), opa_boolean(true)) == 0);
      test("startswith/abcac", opa_value_compare(opa_strings_startswith(opa_string_terminated("abc"), opa_string_terminated("ac")), opa_boolean(false)) == 0);
  
      test("substring/_00", opa_value_compare(opa_strings_substring(opa_string_terminated(""), opa_number_int(0), opa_number_int(0)), opa_string_terminated("")) == 0);
      test("substring/_0-1", opa_value_compare(opa_strings_substring(opa_string_terminated(""), opa_number_int(0), opa_number_int(-1)), opa_string_terminated("")) == 0);
      test("substring/_10", opa_value_compare(opa_strings_substring(opa_string_terminated(""), opa_number_int(1), opa_number_int(0)), opa_string_terminated("")) == 0);
      test("substring/_1-1", opa_value_compare(opa_strings_substring(opa_string_terminated(""), opa_number_int(1), opa_number_int(-1)), opa_string_terminated("")) == 0);
      test("substring/abc1-1", opa_value_compare(opa_strings_substring(opa_string_terminated("abc"), opa_number_int(1), opa_number_int(-1)), opa_string_terminated("bc")) == 0);
      test("substring/abc10", opa_value_compare(opa_strings_substring(opa_string_terminated("abc"), opa_number_int(1), opa_number_int(0)), opa_string_terminated("")) == 0);
      test("substring/abc11", opa_value_compare(opa_strings_substring(opa_string_terminated("abc"), opa_number_int(1), opa_number_int(1)), opa_string_terminated("b")) == 0);
      test("substring/abc12", opa_value_compare(opa_strings_substring(opa_string_terminated("abc"), opa_number_int(1), opa_number_int(2)), opa_string_terminated("bc")) == 0);
      test("substring/abc41", opa_value_compare(opa_strings_substring(opa_string_terminated("abc"), opa_number_int(4), opa_number_int(1)), opa_string_terminated("")) == 0);
      test("substring/unicode", opa_value_compare(opa_strings_substring(opa_string_terminated("\xC3\xA5\xC3\xA4\xC3\xB6\x7A"), opa_number_int(1), opa_number_int(1)), opa_string_terminated("\xC3\xA4")) == 0);
      test("substring/unicode", opa_value_compare(opa_strings_substring(opa_string_terminated("\xC3\xA5\xC3\xA4\xC3\xB6\x7A"), opa_number_int(1), opa_number_int(2)), opa_string_terminated("\xC3\xA4\xC3\xB6")) == 0);
      test("substring/unicode", opa_value_compare(opa_strings_substring(opa_string_terminated("\xC3\xA5\xC3\xA4\xC3\xB6\x7A"), opa_number_int(2), opa_number_int(-1)), opa_string_terminated("\xC3\xB6\x7A")) == 0);
  
      test("trim/__", opa_value_compare(opa_strings_trim(opa_string_terminated(""), opa_string_terminated("")), opa_string_terminated("")) == 0);
      test("trim/abcba", opa_value_compare(opa_strings_trim(opa_string_terminated("abc"), opa_string_terminated("ba")), opa_string_terminated("c")) == 0);
  
      test("trim_left/__", opa_value_compare(opa_strings_trim_left(opa_string_terminated(""), opa_string_terminated("")), opa_string_terminated("")) == 0);
      test("trim_left/_a", opa_value_compare(opa_strings_trim_left(opa_string_terminated(""), opa_string_terminated("a")), opa_string_terminated("")) == 0);
      test("trim_left/a_", opa_value_compare(opa_strings_trim_left(opa_string_terminated("a"), opa_string_terminated("")), opa_string_terminated("a")) == 0);
      test("trim_left/aa", opa_value_compare(opa_strings_trim_left(opa_string_terminated("a"), opa_string_terminated("a")), opa_string_terminated("")) == 0);
      test("trim_left/ab", opa_value_compare(opa_strings_trim_left(opa_string_terminated("a"), opa_string_terminated("b")), opa_string_terminated("a")) == 0);
      test("trim_left/aba", opa_value_compare(opa_strings_trim_left(opa_string_terminated("ab"), opa_string_terminated("a")), opa_string_terminated("b")) == 0);
      test("trim_left/abcba", opa_value_compare(opa_strings_trim_left(opa_string_terminated("abc"), opa_string_terminated("ba")), opa_string_terminated("c")) == 0);
      test("trim_left/aeuro dceuro ", opa_value_compare(opa_strings_trim_left(opa_string_terminated("a\xE2\x82\xAC d"), opa_string_terminated("ca\xE2\x82\xAC ")), opa_string_terminated("d")) == 0);
  
      test("trim_prefix/__", opa_value_compare(opa_strings_trim_prefix(opa_string_terminated(""), opa_string_terminated("")), opa_string_terminated("")) == 0);
      test("trim_prefix/_a", opa_value_compare(opa_strings_trim_prefix(opa_string_terminated(""), opa_string_terminated("a")), opa_string_terminated("")) == 0);
      test("trim_prefix/a_", opa_value_compare(opa_strings_trim_prefix(opa_string_terminated("a"), opa_string_terminated("")), opa_string_terminated("a")) == 0);
      test("trim_prefix/aa", opa_value_compare(opa_strings_trim_prefix(opa_string_terminated("a"), opa_string_terminated("a")), opa_string_terminated("")) == 0);
      test("trim_prefix/ab", opa_value_compare(opa_strings_trim_prefix(opa_string_terminated("a"), opa_string_terminated("b")), opa_string_terminated("a")) == 0);
      test("trim_prefix/aba", opa_value_compare(opa_strings_trim_prefix(opa_string_terminated("ab"), opa_string_terminated("a")), opa_string_terminated("b")) == 0);
  
      test("trim_right/__", opa_value_compare(opa_strings_trim_right(opa_string_terminated(""), opa_string_terminated("")), opa_string_terminated("")) == 0);
      test("trim_right/_a", opa_value_compare(opa_strings_trim_right(opa_string_terminated(""), opa_string_terminated("a")), opa_string_terminated("")) == 0);
      test("trim_right/a_", opa_value_compare(opa_strings_trim_right(opa_string_terminated("a"), opa_string_terminated("")), opa_string_terminated("a")) == 0);
      test("trim_right/aa", opa_value_compare(opa_strings_trim_right(opa_string_terminated("a"), opa_string_terminated("a")), opa_string_terminated("")) == 0);
      test("trim_right/ab", opa_value_compare(opa_strings_trim_right(opa_string_terminated("a"), opa_string_terminated("b")), opa_string_terminated("a")) == 0);
      test("trim_right/abb", opa_value_compare(opa_strings_trim_right(opa_string_terminated("ab"), opa_string_terminated("b")), opa_string_terminated("a")) == 0);
      test("trim_right/abccb", opa_value_compare(opa_strings_trim_right(opa_string_terminated("abc"), opa_string_terminated("cb")), opa_string_terminated("a")) == 0);
      test("trim_right/daeuro ceuro ", opa_value_compare(opa_strings_trim_right(opa_string_terminated("da\xE2\x82\xAC "), opa_string_terminated("ca\xE2\x82\xAC ")), opa_string_terminated("d")) == 0);
  
      test("trim_suffix/__", opa_value_compare(opa_strings_trim_suffix(opa_string_terminated(""), opa_string_terminated("")), opa_string_terminated("")) == 0);
      test("trim_suffix/_a", opa_value_compare(opa_strings_trim_suffix(opa_string_terminated(""), opa_string_terminated("a")), opa_string_terminated("")) == 0);
      test("trim_suffix/a_", opa_value_compare(opa_strings_trim_suffix(opa_string_terminated("a"), opa_string_terminated("")), opa_string_terminated("a")) == 0);
      test("trim_suffix/aa", opa_value_compare(opa_strings_trim_suffix(opa_string_terminated("a"), opa_string_terminated("a")), opa_string_terminated("")) == 0);
      test("trim_suffix/ab", opa_value_compare(opa_strings_trim_suffix(opa_string_terminated("a"), opa_string_terminated("b")), opa_string_terminated("a")) == 0);
      test("trim_suffix/abb", opa_value_compare(opa_strings_trim_suffix(opa_string_terminated("ab"), opa_string_terminated("b")), opa_string_terminated("a")) == 0);
  
      test("trim_space/_", opa_value_compare(opa_strings_trim_space(opa_string_terminated("")), opa_string_terminated("")) == 0);
      test("trim_space/a", opa_value_compare(opa_strings_trim_space(opa_string_terminated("a")), opa_string_terminated("a")) == 0);
      test("trim_space/_a", opa_value_compare(opa_strings_trim_space(opa_string_terminated(" a")), opa_string_terminated("a")) == 0);
      test("trim_space/a_", opa_value_compare(opa_strings_trim_space(opa_string_terminated("a ")), opa_string_terminated("a")) == 0);
      test("trim_space/_a_", opa_value_compare(opa_strings_trim_space(opa_string_terminated(" a ")), opa_string_terminated("a")) == 0);
      test("trim_space/______a_b_c______", opa_value_compare(opa_strings_trim_space(opa_string_terminated("\t\n\v\f\r a b c \r\f\v\n\t")), opa_string_terminated("a b c")) == 0);
      test("trim_space/euro", opa_value_compare(opa_strings_trim_space(opa_string_terminated("\xE2\x82\xAC")), opa_string_terminated("\xE2\x82\xAC")) == 0);
      test("trim_space/_euro_", opa_value_compare(opa_strings_trim_space(opa_string_terminated(" \xE2\x82\xAC ")), opa_string_terminated("\xE2\x82\xAC")) == 0);
      test("trim_space/a_euro_", opa_value_compare(opa_strings_trim_space(opa_string_terminated("a \xE2\x82\xAC ")), opa_string_terminated("a \xE2\x82\xAC")) == 0);
      test("trim_space/_euro_a", opa_value_compare(opa_strings_trim_space(opa_string_terminated(" \xE2\x82\xAC a")), opa_string_terminated("\xE2\x82\xAC a")) == 0);
      test("trim_space/ogham_a", opa_value_compare(opa_strings_trim_space(opa_string_terminated("\xe1\x9a\x80 a")), opa_string_terminated("a")) == 0);
      test("trim_space/oghamenspace_a", opa_value_compare(opa_strings_trim_space(opa_string_terminated("\xe1\x9a\x80\xe2\x80\x82 a")), opa_string_terminated("a")) == 0);
      test("trim_space/a_oghamenspace_a", opa_value_compare(opa_strings_trim_space(opa_string_terminated("a \xe1\x9a\x80\xe2\x80\x82")), opa_string_terminated("a")) == 0);
  
      test("lower/_", opa_value_compare(opa_strings_lower(opa_string_terminated("")), opa_string_terminated("")) == 0);
      test("lower/a", opa_value_compare(opa_strings_lower(opa_string_terminated("a")), opa_string_terminated("a")) == 0);
      test("lower/A", opa_value_compare(opa_strings_lower(opa_string_terminated("A")), opa_string_terminated("a")) == 0);
      test("lower/AbCd", opa_value_compare(opa_strings_lower(opa_string_terminated("AbCd")), opa_string_terminated("abcd")) == 0);
      test("lower/utf-8", opa_value_compare(opa_strings_lower(opa_string_terminated("\xc4\x80")), opa_string_terminated("\xc4\x81")) == 0);
      test("lower/utf-8", opa_value_compare(opa_strings_lower(opa_string_terminated("\xc9\x83")), opa_string_terminated("\xc6\x80")) == 0);
  
      test("upper/_", opa_value_compare(opa_strings_upper(opa_string_terminated("")), opa_string_terminated("")) == 0);
      test("upper/a", opa_value_compare(opa_strings_upper(opa_string_terminated("a")), opa_string_terminated("A")) == 0);
      test("upper/A", opa_value_compare(opa_strings_upper(opa_string_terminated("A")), opa_string_terminated("A")) == 0);
      test("upper/AbCd", opa_value_compare(opa_strings_upper(opa_string_terminated("AbCd")), opa_string_terminated("ABCD")) == 0);
      test("upper/utf-8", opa_value_compare(opa_strings_upper(opa_string_terminated("\xc4\x81")), opa_string_terminated("\xc4\x80")) == 0);
      test("upper/utf-8", opa_value_compare(opa_strings_upper(opa_string_terminated("\xc6\x80")), opa_string_terminated("\xc9\x83")) == 0);
  }
  
  WASM_EXPORT(test_numbers_range)
  void test_numbers_range(void)
  {
      opa_value *a = opa_number_int(10);
      opa_value *b = opa_number_int(12);
  
      opa_value *exp = opa_array();
      opa_array_t *arr = opa_cast_array(exp);
      opa_array_append(arr, opa_number_int(10));
      opa_array_append(arr, opa_number_int(11));
      opa_array_append(arr, opa_number_int(12));
  
      test("number.range/ascending", opa_value_compare(opa_numbers_range(a, b), exp) == 0);
  
      opa_value *reversed = opa_array();
      arr = opa_cast_array(reversed);
      opa_array_append(arr, opa_number_int(12));
      opa_array_append(arr, opa_number_int(11));
      opa_array_append(arr, opa_number_int(10));
  
      test("numbers.range/descending", opa_value_compare(opa_numbers_range(b, a), reversed) == 0);
      test("numbers.range/bad operand", opa_numbers_range(opa_string_terminated("foo"), opa_number_int(10)) == NULL);
      test("numbers.range/bad operand", opa_numbers_range(opa_number_int(10), opa_string_terminated("foo")) == NULL);
  }
  
  WASM_EXPORT(test_to_number)
  void test_to_number(void)
  {
      test("to_number/null", opa_value_compare(opa_to_number(opa_null()), opa_number_int(0)) == 0);
      test("to_number/false", opa_value_compare(opa_to_number(opa_boolean(false)), opa_number_int(0)) == 0);
      test("to_number/true", opa_value_compare(opa_to_number(opa_boolean(true)), opa_number_int(1)) == 0);
      test("to_number/nop", opa_value_compare(opa_to_number(opa_number_int(1)), opa_number_int(1)) == 0);
      test("to_number/integer", opa_value_compare(opa_to_number(opa_string_terminated("10")), opa_number_int(10)) == 0);
      test("to_number/float", opa_value_compare(opa_to_number(opa_string_terminated("3.5")), opa_number_float(3.5)) == 0);
      test("to_number/bad string", opa_to_number(opa_string_terminated("deadbeef")) == NULL);
      test("to_number/bad value", opa_to_number(opa_array()) == NULL);
  }
  
  WASM_EXPORT(test_cidr_contains)
  void test_cidr_contains(void)
  {
      test("cidr/contains", opa_value_compare(opa_cidr_contains(opa_string_terminated("10.0.0.0/8"), opa_string_terminated("10.1.0.0/24")), opa_boolean(true)) == 0);
      test("cidr/contains", opa_value_compare(opa_cidr_contains(opa_string_terminated("172.17.0.0/24"), opa_string_terminated("172.17.0.0/16")), opa_boolean(false)) == 0);
      test("cidr/contains", opa_value_compare(opa_cidr_contains(opa_string_terminated("10.0.0.0/8"), opa_string_terminated("192.168.1.0/24")), opa_boolean(false)) == 0);
      test("cidr/contains", opa_value_compare(opa_cidr_contains(opa_string_terminated("10.0.0.0/8"), opa_string_terminated("10.1.1.1/32")), opa_boolean(true)) == 0);
      test("cidr/contains", opa_value_compare(opa_cidr_contains(opa_string_terminated("2001:4860:4860::8888/32"), opa_string_terminated("2001:4860:4860:1234::8888/40")), opa_boolean(true)) == 0);
      test("cidr/contains", opa_value_compare(opa_cidr_contains(opa_string_terminated("2001:4860:4860::8888/32"), opa_string_terminated("2001:4860:4860:1234:5678:1234:5678:8888/128")), opa_boolean(true)) == 0);
      test("cidr/contains", opa_value_compare(opa_cidr_contains(opa_string_terminated("2001:4860::/96"), opa_string_terminated("2001:4860::/32")), opa_boolean(false)) == 0);
      test("cidr/contains", opa_value_compare(opa_cidr_contains(opa_string_terminated("2001:4860::/32"), opa_string_terminated("fd1e:5bfe:8af3:9ddc::/64")), opa_boolean(false)) == 0);
      test("cidr/contains", opa_cidr_contains(opa_string_terminated("not-a-cidr"), opa_string_terminated("192.168.1.67")) == NULL);
      test("cidr/contains", opa_cidr_contains(opa_string_terminated("192.168.1.0/28"), opa_string_terminated("not-a-cidr")) == NULL);
      test("cidr/contains", opa_value_compare(opa_cidr_contains(opa_string_terminated("10.0.0.0/8"), opa_string_terminated("10.1.2.3")), opa_boolean(true)) == 0);
      test("cidr/contains", opa_value_compare(opa_cidr_contains(opa_string_terminated("10.0.0.0/8"), opa_string_terminated("192.168.1.1")), opa_boolean(false)) == 0);
  
      test("cidr/contains", opa_value_compare(opa_cidr_intersects(opa_string_terminated("192.168.1.0/25"), opa_string_terminated("192.168.1.64/25")), opa_boolean(true)) == 0);
      test("cidr/contains", opa_value_compare(opa_cidr_intersects(opa_string_terminated("192.168.1.0/24"), opa_string_terminated("192.168.2.0/24")), opa_boolean(false)) == 0);
      test("cidr/contains", opa_value_compare(opa_cidr_intersects(opa_string_terminated("fd1e:5bfe:8af3:9ddc::/64"), opa_string_terminated("fd1e:5bfe:8af3:9ddc:1111::/72")), opa_boolean(true)) == 0);
      test("cidr/contains", opa_value_compare(opa_cidr_intersects(opa_string_terminated("fd1e:5bfe:8af3:9ddc::/64"), opa_string_terminated("2001:4860:4860::8888/32")), opa_boolean(false)) == 0);
      test("cidr/contains", opa_cidr_intersects(opa_string_terminated("not-a-cidr"), opa_string_terminated("192.168.1.0/24")) == NULL);
      test("cidr/contains", opa_cidr_intersects(opa_string_terminated("192.168.1.0/28"), opa_string_terminated("not-a-cidr")) == NULL);
  }
  
  opa_value *__new_value_path(int sz, ...)
  {
      va_list ap;
      opa_value *path = opa_array();
  
      va_start(ap, sz);
  
      for (int i = 0; i < sz; i++)
      {
          const char* p = va_arg(ap, const char*);
          opa_array_append(opa_cast_array(path), opa_string_terminated(p));
      }
  
      va_end(ap);
  
      return path;
  }
  
  WASM_EXPORT(test_opa_value_add_path)
  void test_opa_value_add_path() {
      opa_value *data = opa_object();
      opa_value *update = opa_object();
      opa_value *path;
      opa_errc rc;
  
      opa_object_insert(opa_cast_object(update), opa_string_terminated("a"), opa_number_int(1));
  
      rc = opa_value_add_path(data, opa_array(), update);
      test_errc_eq("empty_path_rc", OPA_ERR_INVALID_PATH, rc);
  
      // Setup base document
      data = opa_object();
      opa_object_insert(opa_cast_object(data), opa_string_terminated("b"), opa_number_int(2));
      opa_object_insert(opa_cast_object(data), opa_string_terminated("c"), opa_number_int(3));
      opa_object_insert(opa_cast_object(data), opa_string_terminated("d"), opa_number_int(4));
  
      // Upsert into existing object path
      update = opa_object();
      opa_object_insert(opa_cast_object(update), opa_string_terminated("x"), opa_number_int(5));
  
      path = __new_value_path(1, "b");
      rc = opa_value_add_path(data, path, update);
      test_errc_eq("overwrite_sub_path_rc", OPA_ERR_OK, rc);
      test_str_eq("overwrite_sub_path", "{\"d\":4,\"c\":3,\"b\":{\"x\":5}}", opa_json_dump(data))
  
      // Upsert w/ creating nested path
      update = opa_object();
      opa_object_insert(opa_cast_object(update), opa_string_terminated("foo"), opa_number_int(123));
  
      path = __new_value_path(5, "b", "y", "z", "p", "q");
      rc = opa_value_add_path(data, path, update);
      test_errc_eq("mkdir_rc", OPA_ERR_OK, rc);
      char *exp = "{\"d\":4,\"c\":3,\"b\":{\"y\":{\"z\":{\"p\":{\"q\":{\"foo\":123}}}},\"x\":5}}";
      test_str_eq("mkdir", exp, opa_json_dump(data));
  
      // NULL path
      update = opa_object();
      rc = opa_value_add_path(update, NULL, opa_object());
      test_errc_eq("null_path_rc", OPA_ERR_INVALID_PATH, rc);
      test_str_eq("null_path_unchanged", exp, opa_json_dump(data));
  
      // non-array path types
      rc = opa_value_add_path(update, opa_string_terminated("foo"), opa_object());
      test_errc_eq("invalid_string_path_rc", OPA_ERR_INVALID_PATH, rc);
      test_str_eq("invalid_string_path_unchanged", exp, opa_json_dump(data));
  
      rc = opa_value_add_path(update, opa_number_int(1), opa_object());
      test_errc_eq("invalid_number_path_rc", OPA_ERR_INVALID_PATH, rc);
      test_str_eq("invalid_number_path_unchanged", exp, opa_json_dump(data));
  
      rc = opa_value_add_path(update, opa_set(), opa_object());
      test_errc_eq("invalid_set_path_rc", OPA_ERR_INVALID_PATH, rc);
      test_str_eq("invalid_set_path_unchanged", exp, opa_json_dump(data));
  
      // invalid nested object type
      opa_value *base = opa_object();
      opa_value *invalid_node = opa_set();
      opa_set_add(opa_cast_set(invalid_node), opa_string_terminated("y"));
      opa_object_insert(opa_cast_object(base), opa_string_terminated("x"), invalid_node);
  
      exp = "{\"x\":[\"y\"]}";
      path = __new_value_path(3, "x", "y", "z");
  
      rc = opa_value_add_path(base, path, opa_object());
      test_errc_eq("invalid_nested_object_in_path_rc", OPA_ERR_INVALID_TYPE, rc);
      test_str_eq("invalid_nested_object_in_path_unchanged", exp, opa_json_dump(base));
  
      // invalid nested object type leaf
      base = opa_object();
      opa_object_insert(opa_cast_object(base), opa_string_terminated("x"), opa_number_int(5));
      exp = "{\"x\":5}";
      path = __new_value_path(3, "x", "y", "z");
  
      rc = opa_value_add_path(base, path, opa_object());
      test_errc_eq("invalid_nested_object_at_path_end_rc", OPA_ERR_INVALID_TYPE, rc);
      test_str_eq("invalid_nested_object_at_path_end_unchanged", exp, opa_json_dump(base));
  }
  
  WASM_EXPORT(test_opa_object_delete)
  void test_opa_object_delete(void)
  {
      opa_value *data = opa_object();
  
      opa_object_insert(opa_cast_object(data), opa_string_terminated("a"), opa_number_int(1));
      opa_object_insert(opa_cast_object(data), opa_string_terminated("b"), opa_number_int(2));
      opa_object_insert(opa_cast_object(data), opa_string_terminated("c"), opa_number_int(3));
  
      opa_object_remove(opa_cast_object(data), opa_string_terminated("a"));
      test_str_eq("remove_key", "{\"c\":3,\"b\":2}", opa_json_dump(data));
  
      opa_object_remove(opa_cast_object(data), opa_string_terminated("bad key"));
      test_str_eq("remove_unknown_key", "{\"c\":3,\"b\":2}", opa_json_dump(data));
  
      opa_object_remove(opa_cast_object(data), opa_string_terminated("b"));
      opa_object_remove(opa_cast_object(data), opa_string_terminated("c"));
      test_str_eq("remove_all_keys", "{}", opa_json_dump(data));
  
      opa_object_remove(opa_cast_object(data), opa_string_terminated("bad key"));
      test_str_eq("remove_on_empty_obj", "{}", opa_json_dump(data));
  }
  
  WASM_EXPORT(test_opa_value_remove_path)
  void test_opa_value_remove_path(void)
  {
      opa_value *path;
      opa_errc rc;
  
      char *raw = "{\"a\":{\"b\":{\"c\":{\"d\":123}}},\"x\":[1,{\"y\":{\"z\":{}}}]}";
      opa_value *data = opa_json_parse(raw, strlen(raw));
  
      path = opa_array();
      rc = opa_value_remove_path(data, path);
      test_errc_eq("empty_path", OPA_ERR_INVALID_PATH, rc);
  
      // Reset back to full data doc
      data = opa_json_parse(raw, strlen(raw));
  
      path = __new_value_path(1, "foo");
      rc = opa_value_remove_path(data, path);
      test_errc_eq("path_doesnt_exist_rc", OPA_ERR_OK, rc);
      test_str_eq("path_doesnt_exist", raw, opa_json_dump(data));
  
      path = __new_value_path(3, "a", "b", "foo");
      rc = opa_value_remove_path(data, path);
      test_errc_eq("path_doesnt_exist_nested_rc", OPA_ERR_OK, rc);
      test_str_eq("path_doesnt_exist_nested", raw, opa_json_dump(data));
  
      path = __new_value_path(4, "a", "b", "c", "d");
      rc = opa_value_remove_path(data, path);
      test_errc_eq("leaf_rc", OPA_ERR_OK, rc);
      test_str_eq("leaf", "{\"a\":{\"b\":{\"c\":{}}},\"x\":[1,{\"y\":{\"z\":{}}}]}", opa_json_dump(data));
  
      path = __new_value_path(2, "a", "b");
      rc = opa_value_remove_path(data, path);
      test_errc_eq("branch_rc", OPA_ERR_OK, rc);
      test_str_eq("branch", "{\"a\":{},\"x\":[1,{\"y\":{\"z\":{}}}]}", opa_json_dump(data));
  
      path = __new_value_path(1, "a");
      rc = opa_value_remove_path(data, path);
      test_errc_eq("branch_root_rc", OPA_ERR_OK, rc);
      test_str_eq("branch_root", "{\"x\":[1,{\"y\":{\"z\":{}}}]}", opa_json_dump(data));
  
      path = __new_value_path(3, "x", "1", "y");
      rc = opa_value_remove_path(data, path);
      test_errc_eq("invalid_array_path_rc", OPA_ERR_OK, rc);
      test_str_eq("invalid_array_path", "{\"x\":[1,{\"y\":{\"z\":{}}}]}", opa_json_dump(data));
  
      path = opa_array();
      opa_array_append(opa_cast_array(path), opa_string_terminated("x"));
      opa_array_append(opa_cast_array(path), opa_number_int(1));
      opa_array_append(opa_cast_array(path), opa_string_terminated("y"));
      rc = opa_value_remove_path(data, path);
      test_errc_eq("invalid_array_path_rc", OPA_ERR_INVALID_PATH, rc);
      test_str_eq("array_index_path", "{\"x\":[1,{\"y\":{\"z\":{}}}]}", opa_json_dump(data));
  
      rc = opa_value_remove_path(opa_object(), NULL);
      test_errc_eq("null_path_rc", OPA_ERR_INVALID_PATH, rc);
      test_str_eq("array_index_path", "{\"x\":[1,{\"y\":{\"z\":{}}}]}", opa_json_dump(data));
  }
  
  #define ARGS_N(...) ((int)(sizeof((int[]){ __VA_ARGS__ })/sizeof(int)))
  #define BUILD(N, ...) build(N, ARGS_N(__VA_ARGS__), __VA_ARGS__)
  
  typedef struct {
      int length;
      int *positions;
  } sequence;
  
  typedef struct {
      int n;
      sequence *sequences;
  } sequences;
  
  static sequences* build(int n, int args, ...)
  {
      va_list valist;
      va_start(valist, args);
  
      sequences *s = malloc(sizeof(sequences));
      s->n = n;
      s->sequences = malloc(n * sizeof(sequence));
  
      for (int i = 0; i < n; i++) {
          int run_length = args / n;
  
          int *positions = s->sequences[i].positions = malloc(sizeof(int) * run_length);
          int j = 0;
          for (; j < run_length; j++)
          {
              positions[j] = va_arg(valist, int);
          }
          s->sequences[i].length = j;
      }
  
      va_end(valist);
      return s;
  }
  
  static void test_submatch_string(const char *s, sequence *seq, opa_value *result)
  {
      for (int i = 0; i < seq->length; i += 2)
      {
          int start = seq->positions[i];
          int end = seq->positions[i+1];
  
          opa_string_t *match = opa_cast_string(opa_value_get(result, opa_number_int(i/2)));
          test("regex/find_all_string_submatch", opa_strncmp(&s[start], match->v, end-start) == 0);
      }
  }
  
  WASM_EXPORT(test_regex)
  void test_regex(void)
  {
      test("regex/is_valid", opa_value_compare(opa_regex_is_valid(opa_string_terminated(".*")), opa_boolean(true)) == 0);
      test("regex/is_valid_non_string", opa_value_compare(opa_regex_is_valid(opa_number_int(123)), opa_boolean(false)) == 0);
  
      typedef struct {
          const char *pat;
          const char *text;
          sequences *sequences;
      } testcase;
  
      // golang find test cases from https://golang.org/src/regexp/find_test.go
      testcase tests[] = {
          {"", "", BUILD(1, 0, 0)},
          {"^abcdefg", "abcdefg", BUILD(1, 0, 7)},
          {"a+", "baaab", BUILD(1, 1, 4)},
          {"abcd..", "abcdef", BUILD(1, 0, 6)},
          {"a", "a", BUILD(1, 0, 1)},
          {"x", "y", NULL},
          {"b", "abc", BUILD(1, 1, 2)},
          {".", "a", BUILD(1, 0, 1)},
          {".*", "abcdef", BUILD(1, 0, 6)},
          {"^a", "abcde", BUILD(1, 0, 1)},
          {"^", "abcde", BUILD(1, 0, 0)},
          {"$", "abcde", BUILD(1, 5, 5)},
          {"^abcd$", "abcd", BUILD(1, 0, 4)},
          {"^bcd'", "abcdef", NULL},
          {"^abcd$", "abcde", NULL},
          {"a+", "baaab", BUILD(1, 1, 4)},
          {"a*", "baaab", BUILD(3, 0, 0, 1, 4, 5, 5)},
          {"[a-z]+", "abcd", BUILD(1, 0, 4)},
          {"[^a-z]+", "ab1234cd", BUILD(1, 2, 6)},
          {"[a\\-\\]z]+", "az]-bcz", BUILD(2, 0, 4, 6, 7)},
          {"[^\\n]+", "abcd\n", BUILD(1, 0, 4)},
          {"[日本語]+", "日本語日本語", BUILD(1, 0, 18)},
          {"日本語+", "日本語", BUILD(1, 0, 9)},
          {"日本語+", "日本語語語語", BUILD(1, 0, 18)},
          {"()", "", BUILD(1, 0, 0, 0, 0)},
          {"(a)", "a", BUILD(1, 0, 1, 0, 1)},
          {"(.)(.)", "日a", BUILD(1, 0, 4, 0, 3, 3, 4)},
          {"(.*)", "", BUILD(1, 0, 0, 0, 0)},
          {"(.*)", "abcd", BUILD(1, 0, 4, 0, 4)},
          {"(..)(..)", "abcd", BUILD(1, 0, 4, 0, 2, 2, 4)},
          {"(([^xyz]*)(d))", "abcd", BUILD(1, 0, 4, 0, 4, 0, 3, 3, 4)},
          {"((a|b|c)*(d))", "abcd", BUILD(1, 0, 4, 0, 4, 2, 3, 3, 4)},
          {"(((a|b|c)*)(d))", "abcd", BUILD(1, 0, 4, 0, 4, 0, 3, 2, 3, 3, 4)},
          {"\\a\\f\\n\\r\\t\\v", "\a\f\n\r\t\v", BUILD(1, 0, 6)},
          {"[\\a\\f\\n\\r\\t\\v]+", "\a\f\n\r\t\v", BUILD(1, 0, 6)},
  
          {"a*(|(b))c*", "aacc", BUILD(1, 0, 4, 2, 2, -1, -1)},
          {"(.*).*", "ab", BUILD(1, 0, 2, 0, 2)},
          {"[.]", ".", BUILD(1, 0, 1)},
          {"/$", "/abc/", BUILD(1, 4, 5)},
          {"/$", "/abc", NULL},
  
          // multiple matches
          {".", "abc", BUILD(3, 0, 1, 1, 2, 2, 3)},
          {"(.)", "abc", BUILD(3, 0, 1, 0, 1, 1, 2, 1, 2, 2, 3, 2, 3)},
          {".(.)", "abcd", BUILD(2, 0, 2, 1, 2, 2, 4, 3, 4)},
          {"ab*", "abbaab", BUILD(3, 0, 3, 3, 4, 4, 6)},
          {"a(b*)", "abbaab", BUILD(3, 0, 3, 1, 3, 3, 4, 4, 4, 4, 6, 5, 6)},
  
          // fixed bugs
          {"ab$", "cab", BUILD(1, 1, 3)},
          {"axxb$", "axxcb", NULL},
          {"data", "daXY data", BUILD(1, 5, 9)},
          {"da(.)a$", "daXY data", BUILD(1, 5, 9, 7, 8)},
          {"zx+", "zzx", BUILD(1, 1, 3)},
          {"ab$", "abcab", BUILD(1, 3, 5)},
          {"(aa)*$", "a", BUILD(1, 1, 1, -1, -1)},
          {"(?:.|(?:.a))", "", NULL},
          {"(?:A(?:A|a))", "Aa", BUILD(1, 0, 2)},
          {"(?:A|(?:A|a))", "a", BUILD(1, 0, 1)},
          {"(a){0}", "", BUILD(1, 0, 0, -1, -1)},
          {"(?-s)(?:(?:^).)", "\n", NULL},
          {"(?s)(?:(?:^).)", "\n", BUILD(1, 0, 1)},
          {"(?:(?:^).)", "\n", NULL},
          {"\\b", "x", BUILD(2, 0, 0, 1, 1)},
          {"\\b", "xx", BUILD(2, 0, 0, 2, 2)},
          {"\\b", "x y", BUILD(4, 0, 0, 1, 1, 2, 2, 3, 3)},
          {"\\b", "xx yy", BUILD(4, 0, 0, 2, 2, 3, 3, 5, 5)},
          {"\\B", "x", NULL},
          {"\\B", "xx", BUILD(1, 1, 1)},
          {"\\B", "x y", NULL},
          {"\\B", "xx yy", BUILD(2, 1, 1, 4, 4)},
  
          // RE2 tests
          {"[^\\S\\s]", "abcd", NULL},
          {"[^\\S[:space:]]", "abcd", NULL},
          {"[^\\D\\d]", "abcd", NULL},
          {"[^\\D[:digit:]]", "abcd", NULL},
          {"(?i)\\W", "x", NULL},
          {"(?i)\\W", "k", NULL},
          {"(?i)\\W", "s", NULL},
  
          // long set of matches (longer than startSize)
          {
  		".",
  		"qwertyuiopasdfghjklzxcvbnm1234567890",
  		BUILD(36, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10,
                10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20,
                20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30,
                30, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36),
          },
      };
  
      for (int i = 0; i < sizeof(tests) / sizeof(testcase); i++)
      {
          test("regex/match", opa_value_compare(opa_regex_match(opa_string_terminated(tests[i].pat), opa_string_terminated(tests[i].text)), opa_boolean(tests[i].sequences != NULL)) == 0);
      }
  
      for (int i = 0; i < sizeof(tests) / sizeof(testcase); i++)
      {
          opa_value *result = opa_regex_find_all_string_submatch(opa_string_terminated(tests[i].pat), opa_string_terminated(tests[i].text), opa_number_int(-1));
          opa_array_t *arr = opa_cast_array(result);
  
          if (tests[i].sequences == NULL)
          {
              test("regex/find_all_string_submatch (len)", arr->len == 0);
              continue;
          }
  
          test("regex/find_all_string_submatch (len)", arr->len == tests[i].sequences->n);
          for (int j = 0; j < tests[i].sequences->n; j++)
          {
              test_submatch_string(tests[i].text, &tests[i].sequences->sequences[j], opa_value_get(result, opa_number_int(j)));
          }
      }
  }
  
  WASM_EXPORT(test_opa_lookup)
  void test_opa_lookup(void)
  {
      opa_array_t *path1 = opa_cast_array(opa_array());
      opa_array_append(path1, opa_string_terminated("foo"));
      opa_array_append(path1, opa_string_terminated("bar"));
      opa_array_append(path1, opa_string_terminated("baz"));
  
      opa_object_t *mock_mapping = opa_cast_object(opa_object());
      opa_object_t *obj1 = opa_cast_object(opa_object());
      opa_object_insert(obj1, opa_string_terminated("baz"), opa_number_int(1));
      opa_object_t *obj2 = opa_cast_object(opa_object());
      opa_object_insert(obj2, opa_string_terminated("bar"), &obj1->hdr);
      opa_object_insert(mock_mapping, opa_string_terminated("foo"), &obj2->hdr);
  
      opa_value *empty_mapping = opa_object();
  
      opa_object_t *smaller_mapping = opa_cast_object(opa_object());
      opa_object_t *obj3 = opa_cast_object(opa_object());
      opa_object_insert(obj3, opa_string_terminated("bar"), opa_number_int(2));
      opa_object_insert(smaller_mapping, opa_string_terminated("foo"), &obj3->hdr);
  
      test("opa_lookup/hit", opa_lookup(&mock_mapping->hdr, &path1->hdr) == 1);
      test("opa_lookup/miss", opa_lookup(empty_mapping, &path1->hdr) == 0);
      test("opa_lookup/miss/less", opa_lookup(&smaller_mapping->hdr, &path1->hdr) == 0);
  }
  
  WASM_EXPORT(test_opa_mapping_init)
  void test_opa_mapping_init(void)
  {
      opa_string_t *s = opa_cast_string(opa_string_terminated("{\"foo\": {\"bar\": 123}}"));
      opa_mapping_init(s->v, s->len);
  
      opa_array_t *path1 = opa_cast_array(opa_array());
      opa_array_append(path1, opa_string_terminated("foo"));
      opa_array_append(path1, opa_string_terminated("bar"));
      test("opa_mapping_init/opa_lookup_works", opa_mapping_lookup(&path1->hdr) == 123);
  }
  