const instanceTestFactory = [
  [
    "Empty module without imports argument",
    function() {
      return {
        buffer: emptyModuleBinary,
        args: [],
        exports: {},
        verify: () => {},
      };
    }
  ],

  [
    "Empty module with undefined imports argument",
    function() {
      return {
        buffer: emptyModuleBinary,
        args: [undefined],
        exports: {},
        verify: () => {},
      };
    }
  ],

  [
    "Empty module with empty imports argument",
    function() {
      return {
        buffer: emptyModuleBinary,
        args: [{}],
        exports: {},
        verify: () => {},
      };
    }
  ],

  [
    "getter order for imports object",
    function() {
      const builder = new WasmModuleBuilder();
      builder.addImportedGlobal("module", "global1", kWasmI32);
      builder.addImportedGlobal("module2", "global3", kWasmI32);
      builder.addImportedMemory("module", "memory", 0, 128);
      builder.addImportedGlobal("module", "global2", kWasmI32);
      const buffer = builder.toBuffer();
      const order = [];

      const imports = {
        get module() {
          order.push("module getter");
          return {
            get global1() {
              order.push("global1 getter");
              return 0;
            },
            get global2() {
              order.push("global2 getter");
              return 0;
            },
            get memory() {
              order.push("memory getter");
              return new WebAssembly.Memory({ "initial": 64, maximum: 128 });
            },
          }
        },
        get module2() {
          order.push("module2 getter");
          return {
            get global3() {
              order.push("global3 getter");
              return 0;
            },
          }
        },
      };

      const expected = [
        "module getter",
        "global1 getter",
        "module2 getter",
        "global3 getter",
        "module getter",
        "memory getter",
        "module getter",
        "global2 getter",
      ];
      return {
        buffer,
        args: [imports],
        exports: {},
        verify: () => assert_array_equals(order, expected),
      };
    }
  ],

  [
    "imports",
    function() {
      const builder = new WasmModuleBuilder();

      builder.addImport("module", "fn", kSig_v_v);
      builder.addImportedGlobal("module", "global", kWasmI32);
      builder.addImportedMemory("module", "memory", 0, 128);
      builder.addImportedTable("module", "table", 0, 128);

      const buffer = builder.toBuffer();
      const imports = {
        "module": {
          "fn": function() {},
          "global": 0,
          "memory": new WebAssembly.Memory({ "initial": 64, maximum: 128 }),
          "table": new WebAssembly.Table({ "element": "anyfunc", "initial": 64, maximum: 128 }),
        },
        get "module2"() {
          assert_unreached("Should not get modules that are not imported");
        },
      };

      return {
        buffer,
        args: [imports],
        exports: {},
        verify: () => {},
      };
    }
  ],

  [
    "imports with empty module names",
    function() {
      const builder = new WasmModuleBuilder();

      builder.addImport("", "fn", kSig_v_v);
      builder.addImportedGlobal("", "global", kWasmI32);
      builder.addImportedMemory("", "memory", 0, 128);
      builder.addImportedTable("", "table", 0, 128);

      const buffer = builder.toBuffer();
      const imports = {
        "": {
          "fn": function() {},
          "global": 0,
          "memory": new WebAssembly.Memory({ "initial": 64, maximum: 128 }),
          "table": new WebAssembly.Table({ "element": "anyfunc", "initial": 64, maximum: 128 }),
        },
      };

      return {
        buffer,
        args: [imports],
        exports: {},
        verify: () => {},
      };
    }
  ],

  [
    "imports with empty names",
    function() {
      const builder = new WasmModuleBuilder();

      builder.addImport("a", "", kSig_v_v);
      builder.addImportedGlobal("b", "", kWasmI32);
      builder.addImportedMemory("c", "", 0, 128);
      builder.addImportedTable("d", "", 0, 128);

      const buffer = builder.toBuffer();
      const imports = {
        "a": { "": function() {} },
        "b": { "": 0 },
        "c": { "": new WebAssembly.Memory({ "initial": 64, maximum: 128 }) },
        "d": { "": new WebAssembly.Table({ "element": "anyfunc", "initial": 64, maximum: 128 }) },
      };

      return {
        buffer,
        args: [imports],
        exports: {},
        verify: () => {},
      };
    }
  ],

  [
    "exports with empty name: function",
    function() {
      const builder = new WasmModuleBuilder();

      builder
        .addFunction("", kSig_v_d)
        .addBody([])
        .exportFunc();

      const buffer = builder.toBuffer();

      const exports = {
        "": { "kind": "function", "name": "0", "length": 1 },
      };

      return {
        buffer,
        args: [],
        exports,
        verify: () => {},
      };
    }
  ],

  [
    "exports with empty name: table",
    function() {
      const builder = new WasmModuleBuilder();

      builder.setTableBounds(1);
      builder.addExportOfKind("", kExternalTable, 0);

      const buffer = builder.toBuffer();

      const exports = {
        "": { "kind": "table", "length": 1 },
      };

      return {
        buffer,
        args: [],
        exports,
        verify: () => {},
      };
    }
  ],

  [
    "exports with empty name: global",
    function() {
      const builder = new WasmModuleBuilder();

      builder.addGlobal(kWasmI32, true)
        .exportAs("")
        .init = wasmI32Const(7);

      const buffer = builder.toBuffer();

      const exports = {
        "": { "kind": "global", "value": 7 },
      };

      return {
        buffer,
        args: [],
        exports,
        verify: () => {},
      };
    }
  ],

  [
    "No imports",
    function() {
      const builder = new WasmModuleBuilder();

      builder
        .addFunction("fn", kSig_v_d)
        .addBody([])
        .exportFunc();
      builder
        .addFunction("fn2", kSig_v_v)
        .addBody([])
        .exportFunc();

      builder.setTableBounds(1);
      builder.addExportOfKind("table", kExternalTable, 0);

      builder.addGlobal(kWasmI32, true)
        .exportAs("global")
        .init = wasmI32Const(7);
      builder.addGlobal(kWasmF64, true)
        .exportAs("global2")
        .init = wasmF64Const(1.2);

      builder.addMemory(4, 8, true);

      const buffer = builder.toBuffer();

      const exports = {
        "fn": { "kind": "function", "name": "0", "length": 1 },
        "fn2": { "kind": "function", "name": "1", "length": 0 },
        "table": { "kind": "table", "length": 1 },
        "global": { "kind": "global", "value": 7 },
        "global2": { "kind": "global", "value": 1.2 },
        "memory": { "kind": "memory", "size": 4 },
      };

      return {
        buffer,
        args: [],
        exports,
        verify: () => {},
      };
    }
  ],

  [
    "exports and imports",
    function() {
      const value = 102;

      const builder = new WasmModuleBuilder();

      const index = builder.addImportedGlobal("module", "global", kWasmI32);
      builder
        .addFunction("fn", kSig_i_v)
        .addBody([
            kExprGlobalGet,
            index,
            kExprReturn,
        ])
        .exportFunc();

      const buffer = builder.toBuffer();

      const imports = {
        "module": {
          "global": value,
        },
      };

      const exports = {
        "fn": { "kind": "function", "name": "0", "length": 0 },
      };

      return {
        buffer,
        args: [imports],
        exports,
        verify: instance => assert_equals(instance.exports.fn(), value)
      };
    }
  ],

  [
    "i64 exports and imports",
    function() {
      const value = 102n;

      const builder = new WasmModuleBuilder();

      const index = builder.addImportedGlobal("module", "global", kWasmI64);
      builder
        .addFunction("fn", kSig_l_v)
        .addBody([
            kExprGlobalGet,
            index,
            kExprReturn,
        ])
        .exportFunc();

      const index2 = builder.addImportedGlobal("module", "global2", kWasmI64);
      builder.addExportOfKind("global", kExternalGlobal, index2);

      const buffer = builder.toBuffer();

      const imports = {
        "module": {
          "global": value,
          "global2": 2n ** 63n,
        },
      };

      const exports = {
        "fn": { "kind": "function", "name": "0", "length": 0 },
        "global": { "kind": "global", "value": -(2n ** 63n) },
      };

      return {
        buffer,
        args: [imports],
        exports,
        verify: instance => assert_equals(instance.exports.fn(), value)
      };
    }
  ],

  [
    "import with i32-returning function",
    function() {
      const builder = new WasmModuleBuilder();

      const fnIndex = builder.addImport("module", "fn", kSig_i_v);
      const fn2 = builder
        .addFunction("fn2", kSig_v_v)
        .addBody([
            kExprCallFunction,
            fnIndex,
            kExprReturn,
        ])
        .exportFunc();

      const buffer = builder.toBuffer();

      let called = false;
      const imports = {
        "module": {
          "fn": function() {
            called = true;
            return 6n;
          },
        },
      };

      return {
        buffer,
        args: [imports],
        exports: {
          "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 },
        },
        verify: instance => {
          assert_throws_js(TypeError, () => instance.exports.fn2());
          assert_true(called, "Should have called into JS");
        }
      };
    }
  ],

  [
    "import with function that takes and returns i32",
    function() {
      const builder = new WasmModuleBuilder();

      const fnIndex = builder.addImport("module", "fn", kSig_i_i);
      const fn2 = builder
        .addFunction("fn2", kSig_i_v)
        .addBody([
            kExprI32Const, 0x66,
            kExprCallFunction,
            fnIndex,
            kExprReturn,
        ])
        .exportFunc();

      const buffer = builder.toBuffer();

      let called = false;
      const imports = {
        "module": {
          "fn": function(n) {
            called = true;
            assert_equals(n, -26);
            return { valueOf() { return 6; } };
          },
        },
      };

      return {
        buffer,
        args: [imports],
        exports: {
          "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 },
        },
        verify: instance => {
          assert_equals(instance.exports.fn2(), 6);
          assert_true(called, "Should have called into JS");
        }
      };
    }
  ],

  [
    "import with i64-returning function",
    function() {
      const builder = new WasmModuleBuilder();

      const fnIndex = builder.addImport("module", "fn", kSig_l_v);
      const fn2 = builder
        .addFunction("fn2", kSig_v_v)
        .addBody([
            kExprCallFunction,
            fnIndex,
            kExprReturn,
        ])
        .exportFunc();

      const buffer = builder.toBuffer();

      let called = false;
      const imports = {
        "module": {
          "fn": function() {
            called = true;
            return 6;
          },
        },
      };

      return {
        buffer,
        args: [imports],
        exports: {
          "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 },
        },
        verify: instance => {
          assert_throws_js(TypeError, () => instance.exports.fn2());
          assert_true(called, "Should have called into JS");
        }
      };
    }
  ],

  [
    "import with function that takes and returns i64",
    function() {
      const builder = new WasmModuleBuilder();

      const fnIndex = builder.addImport("module", "fn", kSig_l_l);
      const fn2 = builder
        .addFunction("fn2", kSig_l_v)
        .addBody([
            kExprI64Const, 0x66,
            kExprCallFunction,
            fnIndex,
            kExprReturn,
        ])
        .exportFunc();

      const buffer = builder.toBuffer();

      let called = false;
      const imports = {
        "module": {
          "fn": function(n) {
            called = true;
            assert_equals(n, -26n);
            return { valueOf() { return 6n; } };
          },
        },
      };

      return {
        buffer,
        args: [imports],
        exports: {
          "fn2": { "kind": "function", "name": String(fn2.index), "length": 0 },
        },
        verify: instance => {
          assert_equals(instance.exports.fn2(), 6n);
          assert_true(called, "Should have called into JS");
        }
      };
    }
  ],

  [
    "import with i32-taking function",
    function() {
      const builder = new WasmModuleBuilder();

      const fn = builder
        .addFunction("fn", kSig_v_i)
        .addBody([
            kExprReturn,
        ])
        .exportFunc();

      const buffer = builder.toBuffer();

      return {
        buffer,
        args: [],
        exports: {
          "fn": { "kind": "function", "name": String(fn.index), "length": 1 },
        },
        verify: instance => assert_throws_js(TypeError, () => instance.exports.fn(6n))
      };
    }
  ],

  [
    "import with i64-taking function",
    function() {
      const builder = new WasmModuleBuilder();

      const fn = builder
        .addFunction("fn", kSig_v_l)
        .addBody([
            kExprReturn,
        ])
        .exportFunc();

      const buffer = builder.toBuffer();

      return {
        buffer,
        args: [],
        exports: {
          "fn": { "kind": "function", "name": String(fn.index), "length": 1 },
        },
        verify: instance => assert_throws_js(TypeError, () => instance.exports.fn(6))
      };
    }
  ],

  [
    "export i64-returning function",
    function() {
      const builder = new WasmModuleBuilder();

      const fn = builder
        .addFunction("fn", kSig_l_v)
        .addBody([
            kExprI64Const, 0x66,
            kExprReturn,
        ])
        .exportFunc();

      const buffer = builder.toBuffer();

      return {
        buffer,
        args: [],
        exports: {
          "fn": { "kind": "function", "name": String(fn.index), "length": 0 },
        },
        verify: instance => assert_equals(instance.exports.fn(), -26n)
      };
    }
  ],

  [
    "i32 mutable WebAssembly.Global import",
    function() {
      const initial = 102;
      const value = new WebAssembly.Global({ "value": "i32", "mutable": true }, initial);

      const builder = new WasmModuleBuilder();

      const index = builder.addImportedGlobal("module", "global", kWasmI32, true);
      const fn = builder
        .addFunction("fn", kSig_i_v)
        .addBody([
            kExprGlobalGet,
            index,
            kExprReturn,
        ])
        .exportFunc();

      const buffer = builder.toBuffer();

      const imports = {
        "module": {
          "global": value,
        },
      };

      const exports = {
        "fn": { "kind": "function", "name": String(fn.index), "length": 0 },
      };

      return {
        buffer,
        args: [imports],
        exports,
        verify: instance => {
          assert_equals(instance.exports.fn(), initial);
          const after = 201;
          value.value = after;
          assert_equals(instance.exports.fn(), after);
        }
      };
    }
  ],

  [
    "i64 mutable WebAssembly.Global import",
    function() {
      const initial = 102n;
      const value = new WebAssembly.Global({ "value": "i64", "mutable": true }, initial);

      const builder = new WasmModuleBuilder();

      const index = builder.addImportedGlobal("module", "global", kWasmI64, true);
      const fn = builder
        .addFunction("fn", kSig_l_v)
        .addBody([
            kExprGlobalGet,
            index,
            kExprReturn,
        ])
        .exportFunc();

      const buffer = builder.toBuffer();

      const imports = {
        "module": {
          "global": value,
        },
      };

      const exports = {
        "fn": { "kind": "function", "name": String(fn.index), "length": 0 },
      };

      return {
        buffer,
        args: [imports],
        exports,
        verify: instance => {
          assert_equals(instance.exports.fn(), initial);
          const after = 201n;
          value.value = after;
          assert_equals(instance.exports.fn(), after);
        }
      };
    }
  ],

  [
    "Multiple i64 arguments",
    function() {
      const builder = new WasmModuleBuilder();

      const fn = builder
          .addFunction("fn", kSig_l_ll)
          .addBody([
              kExprLocalGet, 1,
          ])
          .exportFunc();

      const buffer = builder.toBuffer();

      const exports = {
        "fn": { "kind": "function", "name": String(fn.index), "length": 2 },
      };

      return {
        buffer,
        args: [],
        exports,
        verify: instance => {
          const fn = instance.exports.fn;
          assert_equals(fn(1n, 0n), 0n);
          assert_equals(fn(1n, 123n), 123n);
          assert_equals(fn(1n, -123n), -123n);
          assert_equals(fn(1n, "5"), 5n);
          assert_throws_js(TypeError, () => fn(1n, 5));
        }
      };
    }
  ],

  [
    "stray argument",
    function() {
      return {
        buffer: emptyModuleBinary,
        args: [{}, {}],
        exports: {},
        verify: () => {}
      };
    }
  ],
];

globalThis.instanceTestFactory = instanceTestFactory;
