Bring the magic of Pulumi to Node v8.11.1, v9.11.1, v6.10.3 (#1167)
* Introduce a simple repetition operator to match expected error messages against actual ones * Convert required and optional objects to use a Map (node v9 compat), improve the error formatting for failed tests * Test node v6, v8, and v9 in CI * Get rid of PULUMI_API env in .travis.yml, it's set from the Travis console now
This commit is contained in:
parent
fe45eb875e
commit
1f5be5f8cd
|
@ -1,14 +1,16 @@
|
|||
# It may be tempting to add parens around each individual clause in this expression, but Travis then builds pushes anyway
|
||||
if: branch = master OR branch =~ ^release/ OR tag IS present
|
||||
jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
env: NODE_VERSION=v6.10.3
|
||||
- os: linux
|
||||
env: NODE_VERSION=v8.11.1
|
||||
- os: osx
|
||||
env: NODE_VERSION=v9.11.1
|
||||
language: go
|
||||
go: 1.9
|
||||
sudo: true # give us 7.5GB and >2 bursted cores.
|
||||
env:
|
||||
- PULUMI_API=https://api.pulumi-staging.io
|
||||
git:
|
||||
depth: false
|
||||
before_install:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
nvm install v6.10.2
|
||||
nvm install ${NODE_VERSION-v8.11.1}
|
||||
|
||||
# Travis sources this script, so we can export variables into the
|
||||
# outer shell, so we don't want to set options like nounset because
|
||||
|
|
|
@ -440,7 +440,7 @@ function createFunctionInfo(
|
|||
function processCapturedVariables(
|
||||
capturedVariables: CapturedVariableMap, throwOnFailure: boolean): void {
|
||||
|
||||
for (const name of Object.keys(capturedVariables)) {
|
||||
for (const name of capturedVariables.keys()) {
|
||||
let value: any;
|
||||
try {
|
||||
value = v8.lookupCapturedVariableValue(func, name, throwOnFailure);
|
||||
|
@ -481,7 +481,7 @@ function createFunctionInfo(
|
|||
function processCapturedVariable(
|
||||
capturedVariables: CapturedVariableMap, name: string, value: any) {
|
||||
|
||||
const properties = capturedVariables[name];
|
||||
const properties = capturedVariables.get(name);
|
||||
const serializedName = getOrCreateEntry(name, undefined, context, serialize);
|
||||
|
||||
// try to only serialize out the properties that were used by the user's code.
|
||||
|
|
|
@ -38,8 +38,7 @@ export interface CapturedPropertyInfo {
|
|||
invoked: boolean;
|
||||
}
|
||||
|
||||
export interface CapturedVariableMap extends Record<string, CapturedPropertyInfo[]> {
|
||||
}
|
||||
export type CapturedVariableMap = Map<string, CapturedPropertyInfo[]>;
|
||||
|
||||
// The set of variables the function attempts to capture. There is a required set an an optional
|
||||
// set. The optional set will not block closure-serialization if we cannot find them, while the
|
||||
|
@ -80,7 +79,7 @@ export function parseFunction(funcString: string): [string, ParsedFunction] {
|
|||
result.capturedVariables = capturedVariables;
|
||||
result.usesNonLexicalThis = usesNonLexicalThis;
|
||||
|
||||
if (result.capturedVariables.required.this) {
|
||||
if (result.capturedVariables.required.has("this")) {
|
||||
return [
|
||||
"arrow function captured 'this'. Assign 'this' to another name outside function and capture that.",
|
||||
result,
|
||||
|
@ -329,8 +328,8 @@ function computeUsesNonLexicalThis(file: ts.SourceFile): boolean {
|
|||
function computeCapturedVariableNames(file: ts.SourceFile): CapturedVariables {
|
||||
// Now that we've parsed the file, compute the free variables, and return them.
|
||||
|
||||
let required: CapturedVariableMap = {};
|
||||
let optional: CapturedVariableMap = {};
|
||||
let required: CapturedVariableMap = new Map();
|
||||
let optional: CapturedVariableMap = new Map();
|
||||
const scopes: Set<string>[] = [];
|
||||
let functionVars: Set<string> = new Set();
|
||||
|
||||
|
@ -346,18 +345,18 @@ function computeCapturedVariableNames(file: ts.SourceFile): CapturedVariables {
|
|||
|
||||
// Now just return all variables whose value is true. Filter out any that are part of the built-in
|
||||
// Node.js global object, however, since those are implicitly availble on the other side of serialization.
|
||||
const result: CapturedVariables = { required: {}, optional: {} };
|
||||
const result: CapturedVariables = { required: new Map(), optional: new Map() };
|
||||
|
||||
for (const key of Object.keys(required)) {
|
||||
if (required[key] && !isBuiltIn(key)) {
|
||||
result.required[key] = required[key].concat(
|
||||
optional.hasOwnProperty(key) ? optional[key] : []);
|
||||
for (const key of required.keys()) {
|
||||
if (required.has(key) && !isBuiltIn(key)) {
|
||||
result.required.set(key, required.get(key)!.concat(
|
||||
optional.has(key) ? optional.get(key)! : []));
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of Object.keys(optional)) {
|
||||
if (optional[key] && !isBuiltIn(key) && !required[key]) {
|
||||
result.optional[key] = optional[key];
|
||||
for (const key of optional.keys()) {
|
||||
if (optional.has(key) && !isBuiltIn(key) && !required.has(key)) {
|
||||
result.optional.set(key, optional.get(key)!);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -393,9 +392,9 @@ function computeCapturedVariableNames(file: ts.SourceFile): CapturedVariables {
|
|||
// "typeof undeclared_id" is legal in JS (and is actually used in libraries). So keep
|
||||
// track that we would like to capture this variable, but mark that capture as optional
|
||||
// so we will not throw if we aren't able to find it in scope.
|
||||
optional[name] = combineProperties(optional[name], capturedProperty);
|
||||
optional.set(name, combineProperties(optional.get(name), capturedProperty));
|
||||
} else {
|
||||
required[name] = combineProperties(required[name], capturedProperty);
|
||||
required.set(name, combineProperties(required.get(name), capturedProperty));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -440,7 +439,7 @@ function computeCapturedVariableNames(file: ts.SourceFile): CapturedVariables {
|
|||
}
|
||||
|
||||
function visitThisExpression(node: ts.ThisExpression): void {
|
||||
required["this"] = combineProperties(required["this"], determineCapturedPropertyInfo(node));
|
||||
required.set("this", combineProperties(required.get("this"), determineCapturedPropertyInfo(node)));
|
||||
}
|
||||
|
||||
function combineProperties(existing: CapturedPropertyInfo[] | undefined,
|
||||
|
@ -519,8 +518,8 @@ function computeCapturedVariableNames(file: ts.SourceFile): CapturedVariables {
|
|||
const savedOptional = optional;
|
||||
const savedFunctionVars = functionVars;
|
||||
|
||||
required = {};
|
||||
optional = {};
|
||||
required = new Map();
|
||||
optional = new Map();
|
||||
functionVars = new Set();
|
||||
scopes.push(new Set());
|
||||
|
||||
|
@ -545,8 +544,8 @@ function computeCapturedVariableNames(file: ts.SourceFile): CapturedVariables {
|
|||
|
||||
// Remove any function-scoped variables that we encountered during the walk.
|
||||
for (const v of functionVars) {
|
||||
delete required[v];
|
||||
delete optional[v];
|
||||
required.delete(v);
|
||||
optional.delete(v);
|
||||
}
|
||||
|
||||
// Restore the prior context and merge our free list with the previous one.
|
||||
|
@ -560,11 +559,10 @@ function computeCapturedVariableNames(file: ts.SourceFile): CapturedVariables {
|
|||
optional = savedOptional;
|
||||
}
|
||||
|
||||
// Record<string, CapturedPropertyInfo[]>
|
||||
function mergeMaps(target: CapturedVariableMap, source: CapturedVariableMap) {
|
||||
for (const key of Object.keys(source)) {
|
||||
const sourcePropInfos = source[key];
|
||||
let targetPropInfos = target[key];
|
||||
for (const key of source.keys()) {
|
||||
const sourcePropInfos = source.get(key)!;
|
||||
let targetPropInfos = target.get(key)!;
|
||||
|
||||
if (sourcePropInfos.length === 0) {
|
||||
// we want to capture everything. Make sure that's reflected in the target.
|
||||
|
@ -578,7 +576,7 @@ function computeCapturedVariableNames(file: ts.SourceFile): CapturedVariables {
|
|||
}
|
||||
}
|
||||
|
||||
target[key] = targetPropInfos;
|
||||
target.set(key, targetPropInfos);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// tslint:disable:max-line-length
|
||||
|
||||
import * as assert from "assert";
|
||||
import { EOL } from "os";
|
||||
import { runtime } from "../../index";
|
||||
import * as resource from "../../resource";
|
||||
import { Output, output } from "../../resource";
|
||||
|
@ -845,11 +846,11 @@ return () => { let x = eval("undefined + null + NaN + Infinity + __filename"); r
|
|||
|
||||
'() => os': closure.spec.js(0,0): captured
|
||||
module 'os' which indirectly referenced
|
||||
function 'getHostname': which could not be serialized because
|
||||
(...)
|
||||
it was a native code function.
|
||||
|
||||
Function code:
|
||||
function getHostname() { [native code] }
|
||||
function (...)() { [native code] }
|
||||
|
||||
Capturing modules can sometimes cause problems.
|
||||
Consider using import('os') or require('os') inside '() => os': closure.spec.js(0,0)`,
|
||||
|
@ -873,11 +874,11 @@ Consider using import('os') or require('os') inside '() => os': closure.spec.js(
|
|||
|
||||
'(a, b, c) => { const v = os; ...': closure.spec.js(0,0): captured
|
||||
module 'os' which indirectly referenced
|
||||
function 'getHostname': which could not be serialized because
|
||||
(...)
|
||||
it was a native code function.
|
||||
|
||||
Function code:
|
||||
function getHostname() { [native code] }
|
||||
function (...)() { [native code] }
|
||||
|
||||
Capturing modules can sometimes cause problems.
|
||||
Consider using import('os') or require('os') inside '(a, b, c) => { const v = os; ...': closure.spec.js(0,0)`,
|
||||
|
@ -903,11 +904,11 @@ Consider using import('os') or require('os') inside '(a, b, c) => { const v = os
|
|||
'handler', a function defined at
|
||||
'() => os': closure.spec.js(0,0): which captured
|
||||
module 'os' which indirectly referenced
|
||||
function 'getHostname': which could not be serialized because
|
||||
(...)
|
||||
it was a native code function.
|
||||
|
||||
Function code:
|
||||
function getHostname() { [native code] }
|
||||
function (...)() { [native code] }
|
||||
|
||||
Capturing modules can sometimes cause problems.
|
||||
Consider using import('os') or require('os') inside '() => os': closure.spec.js(0,0)`,
|
||||
|
@ -927,18 +928,11 @@ Consider using import('os') or require('os') inside '() => os': closure.spec.js(
|
|||
module './bin/tests/util.js' which indirectly referenced
|
||||
function 'assertAsyncThrows': util.js(0,0): which captured
|
||||
module 'assert' which indirectly referenced
|
||||
function 'ok': assert.js(0,0): which referenced
|
||||
function 'AssertionError': assert.js(0,0): which referenced
|
||||
function 'getMessage': assert.js(0,0): which captured
|
||||
module 'util' which indirectly referenced
|
||||
function 'inspect': util.js(0,0): which referenced
|
||||
function 'formatValue': util.js(0,0): which captured
|
||||
variable 'binding' which indirectly referenced
|
||||
function 'isArrayBuffer': which could not be serialized because
|
||||
it was a native code function.
|
||||
(...)
|
||||
it was a native code function.
|
||||
|
||||
Function code:
|
||||
function isArrayBuffer() { [native code] }
|
||||
function (...)() { [native code] }
|
||||
|
||||
Capturing modules can sometimes cause problems.
|
||||
Consider using import('./bin/tests/util.js') or require('./bin/tests/util.js') inside '() => util': closure.spec.js(0,0)`,
|
||||
|
@ -3818,7 +3812,9 @@ return function /*f3*/() {
|
|||
// updated any time this file changes.
|
||||
const regex = /\([0-9]+,[0-9]+\)/g;
|
||||
const withoutLocations = message.replace(regex, "(0,0)");
|
||||
assert.equal(withoutLocations, test.error);
|
||||
if (test.error) {
|
||||
compareErrorText(test.error, withoutLocations);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -3828,3 +3824,71 @@ return function /*f3*/() {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* compareErrorText compares an "expected" error string and an "actual" error string
|
||||
* and issues an error if they do not match.
|
||||
*
|
||||
* This function accepts two repetition operators to make writing tests easier against
|
||||
* error messages that are dependent on the environment:
|
||||
*
|
||||
* * (...) alone on a single line causes the matcher to accept zero or more lines
|
||||
* between the repetition and the next line.
|
||||
* * (...) within in the context of a line causes the matcher to accept zero or more characters
|
||||
* between the repetition and the next character.
|
||||
*
|
||||
* This is useful when testing error messages that you get when capturing bulit-in modules,
|
||||
* because the specific error message differs between Node versions.
|
||||
* @param expected The expected error message string, potentially containing repetitions
|
||||
* @param actual The actual error message string
|
||||
*/
|
||||
function compareErrorText(expected: string, actual: string) {
|
||||
const wildcard = "(...)";
|
||||
|
||||
if (!expected.includes(wildcard)) {
|
||||
// We get a nice diff view if we diff the entire string, so do that
|
||||
// if we didn't get a wildcard.
|
||||
assert.equal(actual, expected);
|
||||
return;
|
||||
}
|
||||
|
||||
const expectedLines = expected.split(EOL);
|
||||
const actualLines = actual.split(EOL);
|
||||
let actualIndex = 0;
|
||||
for (let expectedIndex = 0; expectedIndex < expectedLines.length; expectedIndex++) {
|
||||
const expectedLine = expectedLines[expectedIndex].trim();
|
||||
if (expectedLine === wildcard) {
|
||||
if (expectedIndex + 1 === expectedLines.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextLine = expectedLines[++expectedIndex].trim();
|
||||
while (true) {
|
||||
const actualLine = actualLines[actualIndex++].trim();
|
||||
if (actualLine === nextLine) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (actualIndex === actualLines.length) {
|
||||
assert.fail(`repetition failed to find match: expected terminator ${nextLine}, received ${actual}`);
|
||||
}
|
||||
}
|
||||
} else if (expectedLine.includes(wildcard)) {
|
||||
const line = actualLines[actualIndex++].trim();
|
||||
const index = expectedLine.indexOf(wildcard);
|
||||
const indexAfter = index + wildcard.length;
|
||||
assert.equal(line.substring(0, index), expectedLine.substring(0, index));
|
||||
|
||||
let repetitionIndex = index;
|
||||
for (; repetitionIndex < line.length; repetitionIndex++) {
|
||||
if (line[repetitionIndex] === expectedLine[indexAfter]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert.equal(line.substring(repetitionIndex), expectedLine.substring(indexAfter));
|
||||
} else {
|
||||
assert.equal(actualLines[actualIndex++].trim(), expectedLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue