This change contains an initial implementation of closure serialization built atop V8, rather than our own custom runtime. This requires that we use a Node.js dynamic C++ module, so that we can access the V8 APIs directly. No build magic is required beyond node-gyp. For the most part, this was straight forward, except for one part: we have to use internal V8 APIs. This is required for two reasons: 1) We need access to the function's lexical closure environment, so that we may look up closure variables. Although there is a tantalizingly-close v8::Object::CreationContext, its implementation intentionally pokes through closure contexts in order to recover the Function constructor context instead. That's not what we want. We want the raw, unadulterated Function::context. 2) We need to control the lexical lookups of free variables so that they can look past chained contexts, lexical contexts, withs, and eval-style context extensions. Simply runing a v8::Script, or simulating an eval, doesn't do the trick. Hence, we need to access the unexported v8::internal::Context::Lookup function. There is a third reason which is not yet implemented: free variable calculation. I could use Esprima, or do my own scanner for free variables, but I'd prefer to simply use the V8 parser so that we're using the same JavaScript parser across all components. That too is not part of the v8.h API, so we'll need to crack it open more. To be clear, these are still exported public APIs, in proper headers that are distributed with both Node and V8. They simply aren't part of the "stable" v8.h surface area. As a result, I do expect that maintaining this will be tricky, and I'd like to keep exploring how to do this without needing the internal dependency. For instance, although this works with node-gyp just fine, we will probably be brittle across versions of Node/V8, when the internal APIs might be changing. This will introduce unfortunate versioning headaches (all, hopefully and thankfully, caught at compile-time).
55 lines
2.9 KiB
TypeScript
55 lines
2.9 KiB
TypeScript
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
|
|
|
import * as assert from "assert";
|
|
import { Config, runtime } from "../index";
|
|
|
|
describe("config", () => {
|
|
it("works, basically", () => {
|
|
// Set up some config and then read them back as strings.
|
|
runtime.setConfig("pkg:config:a", "foo");
|
|
runtime.setConfig("pkg:config:bar", "b");
|
|
runtime.setConfig("pkg:config:baz", "baz");
|
|
runtime.setConfig("otherpkg:config:a", "babble");
|
|
runtime.setConfig("otherpkg:config:nothere", "bazzle");
|
|
let config = new Config("pkg:config");
|
|
assert.strictEqual("foo", config.get("a"));
|
|
assert.strictEqual("foo", config.require("a"));
|
|
assert.strictEqual("b", config.get("bar"));
|
|
assert.strictEqual("b", config.require("bar"));
|
|
assert.strictEqual("baz", config.get("baz"));
|
|
assert.strictEqual("baz", config.require("baz"));
|
|
assert.strictEqual(undefined, config.get("nothere"));
|
|
assert.throws(() => { config.require("missing"); });
|
|
});
|
|
it("does strongly typed too!", () => {
|
|
// Set up some config and then read them back as typed things.
|
|
runtime.setConfig("pkg:config:boolf", "false");
|
|
runtime.setConfig("pkg:config:boolt", "true");
|
|
runtime.setConfig("pkg:config:num", "42.333");
|
|
runtime.setConfig("pkg:config:array", "[ 0, false, 2, \"foo\" ]");
|
|
runtime.setConfig("pkg:config:struct", "{ \"foo\": \"bar\", \"mim\": [] }");
|
|
let config = new Config("pkg:config");
|
|
assert.strictEqual(false, config.getBoolean("boolf"));
|
|
assert.strictEqual(false, config.requireBoolean("boolf"));
|
|
assert.strictEqual(true, config.getBoolean("boolt"));
|
|
assert.strictEqual(true, config.requireBoolean("boolt"));
|
|
assert.strictEqual(undefined, config.getBoolean("boolmissing"));
|
|
assert.strictEqual(42.333, config.getNumber("num"));
|
|
assert.strictEqual(42.333, config.requireNumber("num"));
|
|
assert.strictEqual(undefined, config.getNumber("nummissing"));
|
|
assert.deepEqual([ 0, false, 2, "foo" ], config.getObject<any>("array"));
|
|
assert.deepEqual([ 0, false, 2, "foo" ], config.requireObject<any>("array"));
|
|
assert.deepEqual({ "foo": "bar", "mim": [] }, config.getObject<any>("struct"));
|
|
assert.deepEqual({ "foo": "bar", "mim": [] }, config.requireObject<any>("struct"));
|
|
assert.strictEqual(undefined, config.getObject<any>("complexmissing"));
|
|
// ensure requireX throws when missing:
|
|
assert.throws(() => { config.requireBoolean("missing"); });
|
|
assert.throws(() => { config.requireNumber("missing"); });
|
|
assert.throws(() => { config.requireObject<any>("missing"); });
|
|
// ensure getX throws when the value is of the wrong type:
|
|
assert.throws(() => { config.getBoolean("num"); });
|
|
assert.throws(() => { config.getNumber("boolf"); });
|
|
});
|
|
});
|
|
|
|
|