pulumi/sdk/nodejs/tests/runtime/closure.spec.ts

1239 lines
39 KiB
TypeScript

// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
// tslint:disable:max-line-length
import * as assert from "assert";
import { runtime } from "../../index";
import * as resource from "../../resource";
import { Output, output } from "../../resource";
import { assertAsyncThrows, asyncTest } from "../util";
interface ClosureCase {
pre?: () => void; // an optional function to run before this case.
title: string; // a title banner for the test case.
func: Function; // the function whose body and closure to serialize.
expect?: runtime.Closure; // if undefined, error expected; otherwise, the serialized shape.
expectText?: string; // optionally also validate the serialization to JavaScript text.
closureHash?: string; // hash of the closure.
afters?: ClosureCase[]; // an optional list of test cases to run afterwards.
}
// This group of tests ensure that we serialize closures properly.
describe("closure", () => {
describe("hash", () => {
it("is affected by code.", () => {
const closure1: runtime.Closure = {
code: "",
runtime: "",
environment: { },
};
const closure2: runtime.Closure = {
code: "1",
runtime: "",
environment: { },
};
const hash1 = runtime.getClosureHash_forTestingPurposes(closure1);
const hash2 = runtime.getClosureHash_forTestingPurposes(closure2);
assert.notEqual(hash1, hash2);
});
it("is affected by runtime.", () => {
const closure1: runtime.Closure = {
code: "",
runtime: "",
environment: { },
};
const closure2: runtime.Closure = {
code: "",
runtime: "1",
environment: { },
};
const hash1 = runtime.getClosureHash_forTestingPurposes(closure1);
const hash2 = runtime.getClosureHash_forTestingPurposes(closure2);
assert.notEqual(hash1, hash2);
});
it("is affected by module.", () => {
const closure1: runtime.Closure = {
code: "",
runtime: "",
environment: { cap1: { module: "m1" } },
};
const closure2: runtime.Closure = {
code: "",
runtime: "",
environment: { cap1: { module: "m2" } },
};
const hash1 = runtime.getClosureHash_forTestingPurposes(closure1);
const hash2 = runtime.getClosureHash_forTestingPurposes(closure2);
assert.notEqual(hash1, hash2);
});
it("is affected by environment values.", () => {
const closure1: runtime.Closure = {
code: "",
runtime: "",
environment: { },
};
const closure2: runtime.Closure = {
code: "",
runtime: "",
environment: { cap1: { json: 100 } },
};
const hash1 = runtime.getClosureHash_forTestingPurposes(closure1);
const hash2 = runtime.getClosureHash_forTestingPurposes(closure2);
assert.notEqual(hash1, hash2);
});
it("is affected by environment names.", () => {
const closure1: runtime.Closure = {
code: "",
runtime: "",
environment: { cap1: { json: 100 } },
};
const closure2: runtime.Closure = {
code: "",
runtime: "",
environment: { cap2: { json: 100 } },
};
const hash1 = runtime.getClosureHash_forTestingPurposes(closure1);
const hash2 = runtime.getClosureHash_forTestingPurposes(closure2);
assert.notEqual(hash1, hash2);
});
it("is affected by dependency.", () => {
const closure1: runtime.Closure = {
code: "",
runtime: "",
environment: { cap1: { json: 100 } },
};
const closure2: runtime.Closure = {
code: "",
runtime: "",
environment: { cap1: { dep: { json: 100 } } },
};
const hash1 = runtime.getClosureHash_forTestingPurposes(closure1);
const hash2 = runtime.getClosureHash_forTestingPurposes(closure2);
assert.notEqual(hash1, hash2);
});
it("is not affected by environment order.", () => {
const closure1: runtime.Closure = {
code: "",
runtime: "",
environment: { cap1: { json: 100 }, cap2: { json: 200 } },
};
const closure2: runtime.Closure = {
code: "",
runtime: "",
environment: { cap2: { json: 200 }, cap1: { json: 100 } },
};
const hash1 = runtime.getClosureHash_forTestingPurposes(closure1);
const hash2 = runtime.getClosureHash_forTestingPurposes(closure2);
assert.equal(hash1, hash2);
});
it("is different with cyclic and non-cyclic environments.", () => {
const closure1: runtime.Closure = {
code: "",
runtime: "",
environment: { cap1: { json: 100 } },
};
closure1.environment.cap1.closure = closure1;
const closure2: runtime.Closure = {
code: "",
runtime: "",
environment: { cap1: { json: 100 } },
};
const hash1 = runtime.getClosureHash_forTestingPurposes(closure1);
const hash2 = runtime.getClosureHash_forTestingPurposes(closure2);
assert.notEqual(hash1, hash2);
});
});
const cases: ClosureCase[] = [];
// A few simple positive cases for functions/arrows (no captures).
cases.push({
title: "Empty function closure",
// tslint:disable-next-line
func: function () { },
expect: {
code: "(function () { })",
environment: {},
runtime: "nodejs",
},
closureHash: "__2b3ba3b4fb55b6fb500f9e8d7a4e132cec103fe6",
expectText: `exports.handler = __2b3ba3b4fb55b6fb500f9e8d7a4e132cec103fe6;
function __2b3ba3b4fb55b6fb500f9e8d7a4e132cec103fe6() {
return (function() {
with({ }) {
return (function () { })
}
}).apply(undefined, undefined).apply(this, arguments);
}
`,
});
cases.push({
title: "Function closure with this capture",
// tslint:disable-next-line
func: function () { console.log(this); },
expect: {
code: "(function () { console.log(this); })",
environment: {},
runtime: "nodejs",
},
closureHash: "__cd737a7b5f0ddfaee797a6ff6c8b266051f1c30e",
expectText: `exports.handler = __cd737a7b5f0ddfaee797a6ff6c8b266051f1c30e;
function __cd737a7b5f0ddfaee797a6ff6c8b266051f1c30e() {
return (function() {
with({ }) {
return (function () { console.log(this); })
}
}).apply(undefined, undefined).apply(this, arguments);
}
`,
});
cases.push({
title: "Function closure with this and arguments capture",
// tslint:disable-next-line
func: function () { console.log(this + arguments); },
expect: {
code: "(function () { console.log(this + arguments); })",
environment: {},
runtime: "nodejs",
},
closureHash: "__05437ec790248221e1167f1da8e9a9ffbfe11ebf",
expectText: `exports.handler = __05437ec790248221e1167f1da8e9a9ffbfe11ebf;
function __05437ec790248221e1167f1da8e9a9ffbfe11ebf() {
return (function() {
with({ }) {
return (function () { console.log(this + arguments); })
}
}).apply(undefined, undefined).apply(this, arguments);
}
`,
});
cases.push({
title: "Empty arrow closure",
// tslint:disable-next-line
func: () => { },
expect: {
code: "(() => { })",
environment: {},
runtime: "nodejs",
},
closureHash: "__b135b11756da3f7aecaaa23a36898c0d6d2845ab",
expectText: `exports.handler = __b135b11756da3f7aecaaa23a36898c0d6d2845ab;
function __b135b11756da3f7aecaaa23a36898c0d6d2845ab() {
return (function() {
with({ }) {
return (() => { })
}
}).apply(undefined, undefined).apply(this, arguments);
}
`,
});
cases.push({
title: "Arrow closure with this capture",
// tslint:disable-next-line
func: () => { console.log(this); },
expect: {
code: "(() => { console.log(this); })",
environment: { "this": { "module": "./bin/tests/runtime/closure.spec.js" } },
runtime: "nodejs",
},
closureHash: "__7909a569cc754ce6ee42e2eaf967c6a4a86d1dd8",
expectText: `exports.handler = __7909a569cc754ce6ee42e2eaf967c6a4a86d1dd8;
function __7909a569cc754ce6ee42e2eaf967c6a4a86d1dd8() {
return (function() {
with({ }) {
return (() => { console.log(this); })
}
}).apply(require("./bin/tests/runtime/closure.spec.js"), undefined).apply(this, arguments);
}
`,
});
const awaiterClosure = {
closure: {
code: "(function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n})",
environment: {},
runtime: "nodejs",
},
};
const awaiterCode =
`
function __492fe142c8be132f2ccfdc443ed720d77b1ef3a6() {
return (function() {
with({ }) {
return (function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
})
}
}).apply(undefined, undefined).apply(this, arguments);
}
`;
cases.push({
title: "Async lambda that does not capture this",
// tslint:disable-next-line
func: async () => { },
expect: {
code: "(() => __awaiter(this, void 0, void 0, function* () { }))",
environment: { "__awaiter": awaiterClosure },
runtime: "nodejs",
},
closureHash: "__2a83dcc4e3c79da00ade608e1401449fd97f37fe",
expectText: `exports.handler = __2a83dcc4e3c79da00ade608e1401449fd97f37fe;
function __2a83dcc4e3c79da00ade608e1401449fd97f37fe() {
return (function() {
with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {
return (() => __awaiter(this, void 0, void 0, function* () { }))
}
}).apply(undefined, undefined).apply(this, arguments);
}
${awaiterCode}
`,
});
cases.push({
title: "Async lambda that does capture this",
// tslint:disable-next-line
func: async () => { console.log(this); },
expect: {
code: "(() => __awaiter(this, void 0, void 0, function* () { console.log(this); }))",
environment: {
"__awaiter": awaiterClosure,
"this": { "module": "./bin/tests/runtime/closure.spec.js" },
},
runtime: "nodejs",
},
closureHash: "__f7cb93fabbd2f283f184e4cbfd6166ee13ff4969",
expectText: `exports.handler = __f7cb93fabbd2f283f184e4cbfd6166ee13ff4969;
function __f7cb93fabbd2f283f184e4cbfd6166ee13ff4969() {
return (function() {
with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {
return (() => __awaiter(this, void 0, void 0, function* () { console.log(this); }))
}
}).apply(require("./bin/tests/runtime/closure.spec.js"), undefined).apply(this, arguments);
}
${awaiterCode}
`,
});
cases.push({
title: "Async function that does not capture this",
// tslint:disable-next-line
func: async function() { },
expect: {
code: "(function () {\n return __awaiter(this, void 0, void 0, function* () { });\n })",
environment: { "__awaiter": awaiterClosure },
runtime: "nodejs",
},
closureHash: "__777fc5424c69bbec55be2ab6c25c4f5aac7b80e6",
expectText: `exports.handler = __777fc5424c69bbec55be2ab6c25c4f5aac7b80e6;
function __777fc5424c69bbec55be2ab6c25c4f5aac7b80e6() {
return (function() {
with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {
return (function () {
return __awaiter(this, void 0, void 0, function* () { });
})
}
}).apply(undefined, undefined).apply(this, arguments);
}
${awaiterCode}
`,
});
cases.push({
title: "Async function that does capture this",
// tslint:disable-next-line
func: async function () { console.log(this); },
expect: {
code: "(function () {\n return __awaiter(this, void 0, void 0, function* () { console.log(this); });\n })",
environment: { "__awaiter": awaiterClosure },
runtime: "nodejs",
},
closureHash: "__7bddcde28730579e85ca0d9e450a65cad476232c",
expectText: `exports.handler = __7bddcde28730579e85ca0d9e450a65cad476232c;
function __7bddcde28730579e85ca0d9e450a65cad476232c() {
return (function() {
with({ __awaiter: __492fe142c8be132f2ccfdc443ed720d77b1ef3a6 }) {
return (function () {
return __awaiter(this, void 0, void 0, function* () { console.log(this); });
})
}
}).apply(undefined, undefined).apply(this, arguments);
}
${awaiterCode}
`,
});
cases.push({
title: "Arrow closure with this and arguments capture",
// tslint:disable-next-line
func: (function() { return () => { console.log(this + arguments); } }).apply(this, [0, 1]),
expect: {
code: "(() => { console.log(this + arguments); })",
environment: {
this: { module: "./bin/tests/runtime/closure.spec.js" },
arguments: { arr: [{ json: 0 }, { json: 1 }] },
},
runtime: "nodejs",
},
closureHash: "__20d3571e4247f51f0a3abf93f4a7e4cfb8b2f26a",
expectText: `exports.handler = __20d3571e4247f51f0a3abf93f4a7e4cfb8b2f26a;
function __20d3571e4247f51f0a3abf93f4a7e4cfb8b2f26a() {
return (function() {
with({ }) {
return (() => { console.log(this + arguments); })
}
}).apply(require("./bin/tests/runtime/closure.spec.js"), [ 0, 1 ]).apply(this, arguments);
}
`,
});
cases.push({
title: "Arrow closure with this capture inside function closure",
// tslint:disable-next-line
func: function () { () => { console.log(this); } },
expect: {
code: "(function () { () => { console.log(this); }; })",
environment: {},
runtime: "nodejs",
},
closureHash: "__6668edd6db8c98baacaf1a227150aa18ce2ae872",
expectText: `exports.handler = __6668edd6db8c98baacaf1a227150aa18ce2ae872;
function __6668edd6db8c98baacaf1a227150aa18ce2ae872() {
return (function() {
with({ }) {
return (function () { () => { console.log(this); }; })
}
}).apply(undefined, undefined).apply(this, arguments);
}
`,
});
cases.push({
title: "Arrow closure with this and arguments capture inside function closure",
// tslint:disable-next-line
func: function () { () => { console.log(this + arguments); } },
expect: {
code: "(function () { () => { console.log(this + arguments); }; })",
environment: {},
runtime: "nodejs",
},
closureHash: "__de8ce937834140441c7413a7e97b67bda12d7205",
expectText: `exports.handler = __de8ce937834140441c7413a7e97b67bda12d7205;
function __de8ce937834140441c7413a7e97b67bda12d7205() {
return (function() {
with({ }) {
return (function () { () => { console.log(this + arguments); }; })
}
}).apply(undefined, undefined).apply(this, arguments);
}
`,
});
cases.push({
title: "Empty function closure w/ args",
// tslint:disable-next-line
func: function (x: any, y: any, z: any) { },
expect: {
code: "(function (x, y, z) { })",
environment: {},
runtime: "nodejs",
},
closureHash: "__e680605f156fcaa89016e23c51d3e2328602ebad",
});
cases.push({
title: "Empty arrow closure w/ args",
// tslint:disable-next-line
func: (x: any, y: any, z: any) => { },
expect: {
code: "((x, y, z) => { })",
environment: {},
runtime: "nodejs",
},
closureHash: "__dd08d1034bd5f0e06f1269cb79974a636ef9cb13",
});
// Serialize captures.
cases.push({
title: "Doesn't serialize global captures",
func: () => { console.log("Just a global object reference"); },
expect: {
code: `(() => { console.log("Just a global object reference"); })`,
environment: {},
runtime: "nodejs",
},
closureHash: "__47ac0033692c3101b014a1a3c17a4318cf7d4330",
});
{
const wcap = "foo";
const xcap = 97;
const ycap = [ true, -1, "yup" ];
const zcap = {
a: "a",
b: false,
c: [ 0 ],
};
cases.push({
title: "Serializes basic captures",
// tslint:disable-next-line
func: () => { console.log(wcap + `${xcap}` + ycap.length + eval(zcap.a)); },
expect: {
code: "(() => { console.log(wcap + `${xcap}` + ycap.length + eval(zcap.a)); })",
environment: {
wcap: {
json: "foo",
},
xcap: {
json: 97,
},
ycap: {
arr: [
{ json: true },
{ json: -1 },
{ json: "yup" },
],
},
zcap: {
obj: {
a: { json: "a" },
b: { json: false },
c: { arr: [ { json: 0 } ] },
},
},
},
runtime: "nodejs",
},
closureHash: "__a07cae0afeaeddbb97b9f7a372b75aafd3b29d0e",
});
}
{
// tslint:disable-next-line
let nocap1 = 1, nocap2 = 2, nocap3 = 3, nocap4 = 4, nocap5 = 5, nocap6 = 6, nocap7 = 7;
// tslint:disable-next-line
let nocap8 = 8, nocap9 = 9, nocap10 = 10;
// tslint:disable-next-line
let cap1 = 100, cap2 = 200, cap3 = 300, cap4 = 400, cap5 = 500, cap6 = 600, cap7 = 700;
// tslint:disable-next-line
let cap8 = 800;
const functext = `((nocap1, nocap2) => {
let zz = nocap1 + nocap2; // not a capture: args
let yy = nocap3; // not a capture: var later on
if (zz) {
zz += cap1; // true capture
let cap1 = 9; // because let is properly scoped
zz += nocap4; // not a capture
var nocap4 = 7; // because var is function scoped
zz += cap2; // true capture
const cap2 = 33;
var nocap3 = 8; // block the above capture
}
let f1 = (nocap5) => {
yy += nocap5; // not a capture: args
cap3++; // capture
};
let f2 = (function (nocap6) {
zz += nocap6; // not a capture: args
if (cap4) { // capture
yy = 0;
}
});
let www = nocap7(); // not a capture; it is defined below
if (true) {
function nocap7() {
}
}
let [{t: [nocap8]},,nocap9 = "hello",...nocap10] = [{t: [true]},null,undefined,1,2];
let vvv = [nocap8, nocap9, nocap10]; // not a capture; declarations from destructuring
let aaa = { // captures in property and method declarations
[cap5]: cap6,
[cap7]() {
cap8
}
}
})`;
cases.push({
title: "Doesn't serialize non-free variables (but retains frees)",
// tslint:disable-next-line
func: eval(functext),
expect: {
code: functext,
environment: {
cap1: { json: 100 },
cap2: { json: 200 },
cap3: { json: 300 },
cap4: { json: 400 },
cap5: { json: 500 },
cap6: { json: 600 },
cap7: { json: 700 },
cap8: { json: 800 },
},
runtime: "nodejs",
},
closureHash: "__f919744848c6471a841a1de62afe7d3d7f7f208a",
});
}
{
// tslint:disable-next-line
let nocap1 = 1;
// tslint:disable-next-line
let cap1 = 100;
cases.push({
title: "Complex capturing cases #1",
func: () => {
// cap1 is captured here.
// nocap1 introduces a new variable that shadows the outer one.
// tslint:disable-next-line
let [nocap1 = cap1] = [];
console.log(nocap1);
},
expect: {
code: `(() => {
// cap1 is captured here.
// nocap1 introduces a new variable that shadows the outer one.
// tslint:disable-next-line
let [nocap1 = cap1] = [];
console.log(nocap1);
})`,
environment: {
cap1: { json: 100 },
},
runtime: "nodejs",
},
closureHash: "__ef48a2e2962bd53acef1b2cda244ae8c72972c05",
});
}
{
// tslint:disable-next-line
let nocap1 = 1;
// tslint:disable-next-line
let cap1 = 100;
cases.push({
title: "Complex capturing cases #2",
func: () => {
// cap1 is captured here.
// nocap1 introduces a new variable that shadows the outer one.
// tslint:disable-next-line
let {nocap1 = cap1} = {};
console.log(nocap1);
},
expect: {
code: `(() => {
// cap1 is captured here.
// nocap1 introduces a new variable that shadows the outer one.
// tslint:disable-next-line
let { nocap1 = cap1 } = {};
console.log(nocap1);
})`,
environment: {
cap1: { json: 100 },
},
runtime: "nodejs",
},
closureHash: "__b409f3bd837d513df07525bef43e57597154625e",
});
}
{
// tslint:disable-next-line
let nocap1 = 1;
// tslint:disable-next-line
let cap1 = 100;
cases.push({
title: "Complex capturing cases #3",
func: () => {
// cap1 is captured here.
// nocap1 introduces a new variable that shadows the outer one.
// tslint:disable-next-line
let {x: nocap1 = cap1} = {};
console.log(nocap1);
},
expect: {
code: `(() => {
// cap1 is captured here.
// nocap1 introduces a new variable that shadows the outer one.
// tslint:disable-next-line
let { x: nocap1 = cap1 } = {};
console.log(nocap1);
})`,
environment: {
cap1: { json: 100 },
},
runtime: "nodejs",
},
closureHash: "__5fa215795194604118a7543ce20b8e273837ae79",
});
}
cases.push({
title: "Don't capture built-ins",
// tslint:disable-next-line
func: () => { let x: any = eval("undefined + null + NaN + Infinity + __filename"); require("os"); },
expect: {
code: `(() => { let x = eval("undefined + null + NaN + Infinity + __filename"); require("os"); })`,
environment: {},
runtime: "nodejs",
},
closureHash: "__fa1c10acee8dd79b39d0f8109d2bc3252b19619a",
});
{
const os = require("os");
cases.push({
title: "Capture built-in modules as stable references, not serialized values",
func: () => os,
expect: {
code: `(() => os)`,
environment: {
os: {
module: "os",
},
},
runtime: "nodejs",
},
closureHash: "__3fa97b166e39ae989158bb37acfa12c7abc25b53",
});
}
{
const util = require("../util");
cases.push({
title: "Capture user-defined modules as stable references, not serialized values",
func: () => util,
expect: {
code: `(() => util)`,
environment: {
util: {
module: "./bin/tests/util.js",
},
},
runtime: "nodejs",
},
closureHash: "__cd171f28483c78d2a63bdda674a8f577dd4b41db",
});
}
cases.push({
title: "Don't capture catch variables",
// tslint:disable-next-line
func: () => { try { } catch (err) { console.log(err); } },
expect: {
code:
`(() => { try { }
catch (err) {
console.log(err);
} })`,
environment: {},
runtime: "nodejs",
},
closureHash: "__040426f0dc90fa8f115c1a7ed52793515564fa98",
});
// Recursive function serialization.
{
const fff = "fff!";
const ggg = "ggg!";
const xcap = {
fff: function () { console.log(fff); },
ggg: () => { console.log(ggg); },
zzz: {
a: [ (a1: any, a2: any) => { console.log(a1 + a2); } ],
},
};
const func = () => {
xcap.fff();
xcap.ggg();
xcap.zzz.a[0]("x", "y");
};
cases.push({
title: "Serializes recursive function captures",
// tslint:disable-next-line
func: func,
expect: {
code: `(() => {
xcap.fff();
xcap.ggg();
xcap.zzz.a[0]("x", "y");
})`,
environment: {
xcap: {
obj: {
fff: {
closure: {
code: "(function () { console.log(fff); })",
environment: { fff: { json: "fff!" } },
runtime: "nodejs",
},
},
ggg: {
closure: {
code: "(() => { console.log(ggg); })",
environment: { ggg: { json: "ggg!" } },
runtime: "nodejs",
},
},
zzz: {
obj: {
a: {
arr: [
{
closure: {
code: "((a1, a2) => { console.log(a1 + a2); })",
environment: {},
runtime: "nodejs",
},
},
],
},
},
},
},
},
},
runtime: "nodejs",
},
closureHash: "__1b6ae6abb4d8b2676bccb50507a0276f5be90371",
});
}
{
class CapCap {
constructor() {
(<any>this).x = 42;
(<any>this).f = () => { console.log((<any>this).x); };
}
}
// Closing over 'this'. This yields a circular closure.
const cap: any = new CapCap();
const env: runtime.Environment = { "this": {} };
env["this"].obj = {
f: {
closure: {
code: "(() => { console.log(this.x); })",
environment: {
"this": env["this"],
},
runtime: "nodejs",
},
},
x: {
json: 42,
},
};
cases.push({
title: "Serializes `this` capturing arrow functions",
func: cap.f,
expect: {
code: "(() => { console.log(this.x); })",
environment: env,
runtime: "nodejs",
},
closureHash: "__8d564176f3cd517bfe3c6e9d6b4da488a1198c0d",
});
}
cases.push({
title: "Don't serialize `this` in function expressions",
func: function() { return this; },
expect: {
code: `(function () { return this; })`,
environment: {},
runtime: "nodejs",
},
closureHash: "__05dabc231611ca558334d59d661ebfb242b31b5d",
});
const mutable: any = {};
cases.push({
title: "Serialize mutable objects by value at the time of capture (pre-mutation)",
func: function() { return mutable; },
expect: {
code: `(function () { return mutable; })`,
environment: {
"mutable": {
obj: {},
},
},
runtime: "nodejs",
},
closureHash: "__e4a4f2f9ad40ef73c250aa10f0277247adfae473",
afters: [{
pre: () => { mutable.timesTheyAreAChangin = true; },
title: "Serialize mutable objects by value at the time of capture (post-mutation)",
func: function() { return mutable; },
expect: {
code: `(function () { return mutable; })`,
environment: {
"mutable": {
obj: {
"timesTheyAreAChangin": {
json: true,
},
},
},
},
runtime: "nodejs",
},
closureHash: "__18d08ca03253fe3dda134c1e5e5889f514cb3841",
}],
});
{
const v = { d: output(4) };
cases.push({
title: "Output capture",
// tslint:disable-next-line
func: function () { console.log(v); },
expect: {
code: "(function () { console.log(v); })",
environment: {
"v": { "obj": { "d": { "dep": { "json": 4 } } } },
},
runtime: "nodejs",
},
closureHash: "__48592975f6308867ccf82dc02acb984a2eb0d858",
expectText: `exports.handler = __48592975f6308867ccf82dc02acb984a2eb0d858;
function __48592975f6308867ccf82dc02acb984a2eb0d858() {
return (function() {
with({ v: { d: { get: () => 4 } } }) {
return (function () { console.log(v); })
}
}).apply(undefined, undefined).apply(this, arguments);
}
`,
});
}
{
const v = {
d1: output(4),
d2: output("str"),
d3: output(undefined),
d4: output({ a: 1, b: true }),
};
cases.push({
title: "Multiple output capture",
// tslint:disable-next-line
func: function () { console.log(v); },
expect: {
code: "(function () { console.log(v); })",
environment: {
"v": { "obj": {
"d1": { "dep": { "json": 4 } },
"d2": { "dep": { "json": "str" } },
"d3": { "dep": { "json": undefined } },
"d4": { "dep": { "obj": {
"a": { "json": 1 },
"b": { "json": true },
} } },
} },
},
runtime: "nodejs",
},
closureHash: "__010ddd8e314a6fdc60244562536298871169f9fb",
expectText: `exports.handler = __010ddd8e314a6fdc60244562536298871169f9fb;
function __010ddd8e314a6fdc60244562536298871169f9fb() {
return (function() {
with({ v: { d1: { get: () => 4 }, d2: { get: () => "str" }, d3: { get: () => undefined }, d4: { get: () => ({ a: 1, b: true }) } } }) {
return (function () { console.log(v); })
}
}).apply(undefined, undefined).apply(this, arguments);
}
`,
});
}
{
const obj = { method1() { return this.method2(); }, method2: () => { return; } };
cases.push({
title: "Capture object with methods",
// tslint:disable-next-line
func: function () { console.log(obj); },
expect: {
code: "(function () { console.log(obj); })",
environment: {
"obj": {
"obj": {
"method1": {
"closure": {
"code": "(function method1() { return this.method2(); })",
"environment": {},
"runtime": "nodejs",
},
},
"method2": {
"closure": {
"code": "(() => { return; })",
"environment": {},
"runtime": "nodejs",
},
},
},
},
},
runtime: "nodejs",
},
closureHash: "__f0b70e2ec196258725e4b2959cb5ec5b89d4c0e4",
expectText: `exports.handler = __f0b70e2ec196258725e4b2959cb5ec5b89d4c0e4;
function __f0b70e2ec196258725e4b2959cb5ec5b89d4c0e4() {
return (function() {
with({ obj: { method1: __f6abbd7cb31cb232a3250be3014cee3b74db4cfa, method2: __d3e9cc89985f25c6465a39781af4eb9e1c3c7c48 } }) {
return (function () { console.log(obj); })
}
}).apply(undefined, undefined).apply(this, arguments);
}
function __f6abbd7cb31cb232a3250be3014cee3b74db4cfa() {
return (function() {
with({ }) {
return (function method1() { return this.method2(); })
}
}).apply(undefined, undefined).apply(this, arguments);
}
function __d3e9cc89985f25c6465a39781af4eb9e1c3c7c48() {
return (function() {
with({ }) {
return (() => { return; })
}
}).apply(undefined, undefined).apply(this, arguments);
}
`,
});
}
{
class C {
public m() { return this.n(); }
public n() { return 0; }
}
cases.push({
title: "Serialize instance class methods",
func: new C().m,
expect: {
code: "(function m() { return this.n(); })",
environment: { },
runtime: "nodejs",
},
closureHash: "__a5583812bfd698420d9f1872a30b68e59b01ac00",
expectText: `exports.handler = __a5583812bfd698420d9f1872a30b68e59b01ac00;
function __a5583812bfd698420d9f1872a30b68e59b01ac00() {
return (function() {
with({ }) {
return (function m() { return this.n(); })
}
}).apply(undefined, undefined).apply(this, arguments);
}
`,
});
}
{
class C {
public static m() { return this.n(); }
public static n() { return 0; }
}
cases.push({
title: "Serialize static class methods",
func: C.m,
expect: {
code: "(function m() { return this.n(); })",
environment: { },
runtime: "nodejs",
},
closureHash: "__a5583812bfd698420d9f1872a30b68e59b01ac00",
expectText: `exports.handler = __a5583812bfd698420d9f1872a30b68e59b01ac00;
function __a5583812bfd698420d9f1872a30b68e59b01ac00() {
return (function() {
with({ }) {
return (function m() { return this.n(); })
}
}).apply(undefined, undefined).apply(this, arguments);
}
`,
});
}
{
const D = (function () {
// tslint:disable-next-line:no-shadowed-variable
function D() {
// tslint:disable-next-line:semicolon
;
}
(<any>D).m = function () { return this.n(); };
(<any>D).n = function () { return 0; };
return D;
}());
cases.push({
title: "Serialize static class methods (es5 class style)",
func: (<any>D).m,
expect: {
code: "(function () { return this.n(); })",
environment: { },
runtime: "nodejs",
},
closureHash: "__4388dd82f50083d1b18aa1eb2cebd11363fedeb4",
expectText: `exports.handler = __4388dd82f50083d1b18aa1eb2cebd11363fedeb4;
function __4388dd82f50083d1b18aa1eb2cebd11363fedeb4() {
return (function() {
with({ }) {
return (function () { return this.n(); })
}
}).apply(undefined, undefined).apply(this, arguments);
}
`,
});
}
// Make a callback to keep running tests.
let remaining = cases;
while (true) {
const test = remaining.shift();
if (!test) {
return;
}
it(test.title, asyncTest(async () => {
// Run pre-actions.
if (test.pre) {
test.pre();
}
// Invoke the test case.
if (test.expect) {
const closure: runtime.Closure = await runtime.serializeClosure(test.func);
assert.deepEqual(closure, test.expect);
if (test.expectText) {
const text = runtime.serializeJavaScriptText(closure);
assert.equal(text, test.expectText);
}
const closureHash = runtime.getClosureHash_forTestingPurposes(closure);
assert.equal(closureHash, test.closureHash);
} else {
await assertAsyncThrows(async () => {
await runtime.serializeClosure(test.func);
});
}
}));
// Schedule any additional tests.
if (test.afters) {
remaining = test.afters.concat(remaining);
}
}
});