-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add multi-pass environment variable resolution (#2322)
* Dependent variable definition ``` "env": { "envRoot": "apps/tool/builldenv", "arm6.include": "${envRoot}/arm6/include" }, ... "some_config": "${arm6.include} ``` * Additionally, this fixes an open bug that was not reported. If a variable contained "env", "config", or "workspaceFolder" it would not be parsed correctly. If you used a variable ${envRoot} it would match "envR" as the type and "oot" as the variable. This has been resolved. * Also fixes travis-ci to run the unit tests as part of checkin validation
- Loading branch information
1 parent
913f740
commit 7947dd8
Showing
4 changed files
with
278 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
/* -------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All Rights Reserved. | ||
* See 'LICENSE' in the project root for license information. | ||
* ------------------------------------------------------------------------------------------ */ | ||
|
||
import * as assert from "assert"; | ||
import { resolveVariables } from "../../src/common"; | ||
|
||
suite("Common Utility validation", () => { | ||
suite("resolveVariables", () => { | ||
const success: string = "success"; | ||
const home: string = process.env.HOME; | ||
|
||
test("raw input", () => { | ||
const input: string = "test"; | ||
inputAndEnvironment(input, {}) | ||
.shouldResolveTo(input); | ||
}); | ||
|
||
test("raw input with tilde", () => { | ||
inputAndEnvironment("~/test", {}) | ||
.shouldResolveTo(`${home}/test`); | ||
}); | ||
|
||
test("env input with tilde", () => { | ||
inputAndEnvironment("${path}/test", { | ||
path: home | ||
}) | ||
.shouldResolveTo(`${home}/test`); | ||
}); | ||
|
||
test("solo env input resulting in array", () => { | ||
inputAndEnvironment("${test}", { | ||
test: ["foo", "bar"] | ||
}) | ||
.shouldResolveTo("foo;bar"); | ||
}); | ||
|
||
test("mixed raw and env input resulting in array", () => { | ||
const input: string = "baz${test}"; | ||
resolveVariablesWithInput(input) | ||
.withEnvironment({ | ||
test: ["foo", "bar"] | ||
}) | ||
.shouldResolveTo(input); | ||
}); | ||
|
||
test("solo env input not in env config finds process env", () => { | ||
const processKey: string = `cpptoolstests_${Date.now()}`; | ||
const input: string = "foo${" + processKey + "}"; | ||
let actual: string; | ||
try { | ||
process.env[processKey] = "bar"; | ||
actual = resolveVariables(input, {}); | ||
} finally { | ||
delete process.env[processKey]; | ||
} | ||
assert.equal(actual, "foobar"); | ||
}); | ||
|
||
test("env input", () => { | ||
resolveVariablesWithInput("${test}") | ||
.withEnvironment({ | ||
"test": success | ||
}) | ||
.shouldResolveTo(success); | ||
}); | ||
|
||
test("env input mixed with plain text", () => { | ||
resolveVariablesWithInput("${test}bar") | ||
.withEnvironment({ | ||
"test": "foo" | ||
}) | ||
.shouldResolveTo("foobar"); | ||
}); | ||
|
||
test("env input with two variables", () => { | ||
resolveVariablesWithInput("f${a}${b}r") | ||
.withEnvironment({ | ||
a: "oo", | ||
b: "ba" | ||
}) | ||
.shouldResolveTo("foobar"); | ||
}); | ||
|
||
test("env input not in env", () => { | ||
const input: string = "${test}"; | ||
resolveVariablesWithInput(input) | ||
.withEnvironment({}) | ||
.shouldResolveTo(input); | ||
}); | ||
|
||
test("env with macro inside environment definition", () => { | ||
resolveVariablesWithInput("${arm6.include}") | ||
.withEnvironment({ | ||
"envRoot": "apps/tool/buildenv", | ||
"arm6.include": "${envRoot}/arm6/include" | ||
}) | ||
.shouldResolveTo("apps/tool/buildenv/arm6/include"); | ||
}); | ||
|
||
test("env nested with half open variable", () => { | ||
resolveVariablesWithInput("${arm6.include}") | ||
.withEnvironment({ | ||
"envRoot": "apps/tool/buildenv", | ||
"arm6.include": "${envRoot/arm6/include" | ||
}) | ||
.shouldResolveTo("${envRoot/arm6/include"); | ||
}); | ||
|
||
test("env nested with half closed variable", () => { | ||
resolveVariablesWithInput("${arm6.include}") | ||
.withEnvironment({ | ||
"envRoot": "apps/tool/buildenv", | ||
"arm6.include": "envRoot}/arm6/include" | ||
}) | ||
.shouldResolveTo("envRoot}/arm6/include"); | ||
}); | ||
|
||
test("env nested with a cycle", () => { | ||
resolveVariablesWithInput("${a}") | ||
.withEnvironment({ | ||
"a": "${b}", | ||
"b": "${c}", | ||
"c": "${a}" | ||
}) | ||
.shouldResolveTo("${a}"); | ||
}); | ||
|
||
test("env input with 1 level of nested variables anchored at end", () => { | ||
resolveVariablesWithInput("${foo${test}}") | ||
.withEnvironment({ | ||
"foobar": success, | ||
"test": "bar" | ||
}) | ||
.shouldResolveTo("${foo${test}}"); | ||
}); | ||
|
||
test("env input with 1 level of nested variables anchored in the middle", () => { | ||
resolveVariablesWithInput("${f${test}r}") | ||
.withEnvironment({ | ||
"foobar": success, | ||
"test": "ooba" | ||
}) | ||
.shouldResolveTo("${f${test}r}"); | ||
}); | ||
|
||
test("env input with 1 level of nested variable anchored at front", () => { | ||
resolveVariablesWithInput("${${test}bar}") | ||
.withEnvironment({ | ||
"foobar": success, | ||
"test": "foo" | ||
}) | ||
.shouldResolveTo("${${test}bar}"); | ||
}); | ||
|
||
test("env input with 3 levels of nested variables", () => { | ||
resolveVariablesWithInput("${foo${a${b${c}}}}") | ||
.withEnvironment({ | ||
"foobar": success, | ||
"a1": "bar", | ||
"b2": "1", | ||
"c": "2" | ||
}) | ||
.shouldResolveTo("${foo${a${b${c}}}}"); | ||
}); | ||
|
||
test("env input contains env", () => { | ||
resolveVariablesWithInput("${envRoot}") | ||
.shouldLookupSymbol("envRoot"); | ||
}); | ||
|
||
test("env input contains config", () => { | ||
resolveVariablesWithInput("${configRoot}") | ||
.shouldLookupSymbol("configRoot"); | ||
}); | ||
|
||
test("env input contains workspaceFolder", () => { | ||
resolveVariablesWithInput("${workspaceFolderRoot}") | ||
.shouldLookupSymbol("workspaceFolderRoot"); | ||
}); | ||
|
||
test("input contains env.", () => { | ||
resolveVariablesWithInput("${env.Root}") | ||
.shouldLookupSymbol("Root"); | ||
}); | ||
|
||
test("input contains env:", () => { | ||
resolveVariablesWithInput("${env:Root}") | ||
.shouldLookupSymbol("Root"); | ||
}); | ||
|
||
interface ResolveTestFlowEnvironment { | ||
withEnvironment(additionalEnvironment: {[key: string]: string | string[]}): ResolveTestFlowAssert; | ||
shouldLookupSymbol: (key: string) => void; | ||
} | ||
interface ResolveTestFlowAssert { | ||
shouldResolveTo: (x: string) => void; | ||
} | ||
|
||
function resolveVariablesWithInput(input: string): ResolveTestFlowEnvironment { | ||
return { | ||
withEnvironment: (additionalEnvironment: {[key: string]: string | string[]}) => { | ||
return inputAndEnvironment(input, additionalEnvironment); | ||
}, | ||
shouldLookupSymbol: (symbol: string) => { | ||
const environment: {[key: string]: string | string[]} = {}; | ||
environment[symbol] = success; | ||
return inputAndEnvironment(input, environment) | ||
.shouldResolveTo(success); | ||
} | ||
}; | ||
} | ||
|
||
function inputAndEnvironment(input: string, additionalEnvironment: {[key: string]: string | string[]}): ResolveTestFlowAssert { | ||
return { | ||
shouldResolveTo: (expected: string) => { | ||
const actual: string = resolveVariables(input, additionalEnvironment); | ||
const msg: string = `Expected ${expected}. Got ${actual} with input ${input} and environment ${JSON.stringify(additionalEnvironment)}.`; | ||
assert.equal(actual, expected, msg); | ||
} | ||
}; | ||
} | ||
|
||
}); | ||
}); |