-
-
Notifications
You must be signed in to change notification settings - Fork 364
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial work on a vm2 based Dangerfile runner
- Loading branch information
Showing
5 changed files
with
307 additions
and
45 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import * as fs from "fs" | ||
|
||
import { DangerResults } from "../dsl/DangerResults" | ||
import { DangerContext } from "../runner/Dangerfile" | ||
import { Path } from "./types" | ||
|
||
import { NodeVM, VMOptions } from "vm2" | ||
|
||
/** | ||
* Executes a Dangerfile at a specific path, with a context. | ||
* The values inside a Danger context are applied as globals to the Dangerfiles runtime. | ||
* | ||
* @param {DangerContext} dangerfileContext the global danger context | ||
*/ | ||
export async function createDangerfileRuntimeEnvironment(dangerfileContext: DangerContext): Promise<VMOptions> { | ||
const context = dangerfileContext | ||
|
||
const sandbox = {} | ||
// Adds things like fail, warn ... to global | ||
for (const prop in context) { | ||
if (context.hasOwnProperty(prop)) { | ||
const anyContext: any = context | ||
sandbox[prop] = anyContext[prop] | ||
} | ||
} | ||
|
||
return { | ||
sandbox, | ||
} | ||
} | ||
|
||
/** | ||
* Executes a Dangerfile at a specific path, with a context. | ||
* The values inside a Danger context are applied as globals to the Dangerfiles runtime. | ||
* | ||
* @param {string} filename the file path for the dangerfile | ||
* @param {any} environment the results of createDangerfileRuntimeEnvironment | ||
* @returns {DangerResults} the results of the run | ||
*/ | ||
export async function runDangerfileEnvironment(filename: Path, environment: VMOptions): Promise<DangerResults> { | ||
const vm = new NodeVM(environment) | ||
|
||
// Require our dangerfile | ||
const originalContents = fs.readFileSync(filename).toString() | ||
const content = cleanDangerfile(originalContents) | ||
vm.run(content, filename) | ||
|
||
const results = environment.sandbox!.results! | ||
await Promise.all( | ||
results.scheduled.map((fnOrPromise: any) => { | ||
if (fnOrPromise instanceof Promise) { | ||
return fnOrPromise | ||
} | ||
if (fnOrPromise.length === 1) { | ||
// callback-based function | ||
return new Promise(res => fnOrPromise(res)) | ||
} | ||
return fnOrPromise() | ||
}) | ||
) | ||
return { | ||
fails: results.fails, | ||
warnings: results.warnings, | ||
messages: results.messages, | ||
markdowns: results.markdowns, | ||
} | ||
} | ||
|
||
// https://regex101.com/r/dUq4yB/1 | ||
const requirePattern = /^.* require\(('|")danger('|")\);?$/gm | ||
// https://regex101.com/r/dUq4yB/2 | ||
const es6Pattern = /^.* from ('|")danger('|");?$/gm | ||
|
||
/** | ||
* Updates a Dangerfile to remove the import for Danger | ||
* @param {string} contents the file path for the dangerfile | ||
* @returns {string} the revised Dangerfile | ||
*/ | ||
export function cleanDangerfile(contents: string): string { | ||
return contents.replace(es6Pattern, "// Removed import").replace(requirePattern, "// Removed require") | ||
} |
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,187 @@ | ||
import { contextForDanger } from "../Dangerfile" | ||
import { createDangerfileRuntimeEnvironment, runDangerfileEnvironment, cleanDangerfile } from "../DangerfileRunnerTwo" | ||
|
||
import { FakeCI } from "../../ci_source/providers/Fake" | ||
import { FakePlatform } from "../../platforms/FakePlatform" | ||
import { Executor } from "../Executor" | ||
|
||
import * as os from "os" | ||
import * as fs from "fs" | ||
|
||
import { resolve } from "path" | ||
const fixtures = resolve(__dirname, "fixtures") | ||
|
||
/** | ||
* Sets up an example context | ||
* @returns {Promise<DangerContext>} a context | ||
*/ | ||
async function setupDangerfileContext() { | ||
const platform = new FakePlatform() | ||
const config = { | ||
stdoutOnly: false, | ||
verbose: false, | ||
} | ||
|
||
const exec = new Executor(new FakeCI({}), platform, config) | ||
|
||
platform.getPlatformGitRepresentation = jest.fn() | ||
platform.getPlatformDSLRepresentation = jest.fn() | ||
|
||
const dsl = await exec.dslForDanger() | ||
return contextForDanger(dsl) | ||
} | ||
|
||
describe("with fixtures", () => { | ||
it("handles a blank Dangerfile", async () => { | ||
const context = await setupDangerfileContext() | ||
const runtime = await createDangerfileRuntimeEnvironment(context) | ||
const results = await runDangerfileEnvironment(resolve(fixtures, "__DangerfileEmpty.js"), runtime) | ||
|
||
expect(results).toEqual({ | ||
fails: [], | ||
markdowns: [], | ||
messages: [], | ||
warnings: [], | ||
}) | ||
}) | ||
|
||
it("handles a full set of messages", async () => { | ||
const context = await setupDangerfileContext() | ||
const runtime = await createDangerfileRuntimeEnvironment(context) | ||
const results = await runDangerfileEnvironment(resolve(fixtures, "__DangerfileFullMessages.js"), runtime) | ||
|
||
expect(results).toEqual({ | ||
fails: [{ message: "this is a failure" }], | ||
markdowns: ["this is a *markdown*"], | ||
messages: [{ message: "this is a message" }], | ||
warnings: [{ message: "this is a warning" }], | ||
}) | ||
}) | ||
|
||
it("handles a failing dangerfile", async () => { | ||
const context = await setupDangerfileContext() | ||
const runtime = await createDangerfileRuntimeEnvironment(context) | ||
|
||
try { | ||
await runDangerfileEnvironment(resolve(fixtures, "__DangerfileBadSyntax.js"), runtime) | ||
throw new Error("Do not get to this") | ||
} catch (e) { | ||
// expect(e.message === ("Do not get to this")).toBeFalsy() | ||
expect(e.message).toEqual("hello is not defined") | ||
} | ||
}) | ||
|
||
it("handles relative imports correctly", async () => { | ||
const context = await setupDangerfileContext() | ||
const runtime = await createDangerfileRuntimeEnvironment(context) | ||
await runDangerfileEnvironment(resolve(fixtures, "__DangerfileImportRelative.js"), runtime) | ||
}) | ||
|
||
it("handles scheduled (async) code", async () => { | ||
const context = await setupDangerfileContext() | ||
const runtime = await createDangerfileRuntimeEnvironment(context) | ||
const results = await runDangerfileEnvironment(resolve(fixtures, "__DangerfileScheduled.js"), runtime) | ||
expect(results).toEqual({ | ||
fails: [], | ||
messages: [], | ||
markdowns: [], | ||
warnings: [{ message: "Asynchronous Warning" }], | ||
}) | ||
}) | ||
|
||
it("handles multiple scheduled statements and all message types", async () => { | ||
const context = await setupDangerfileContext() | ||
const runtime = await createDangerfileRuntimeEnvironment(context) | ||
const results = await runDangerfileEnvironment(resolve(fixtures, "__DangerfileMultiScheduled.js"), runtime) | ||
expect(results).toEqual({ | ||
fails: [{ message: "Asynchronous Failure" }], | ||
messages: [{ message: "Asynchronous Message" }], | ||
markdowns: ["Asynchronous Markdown"], | ||
warnings: [{ message: "Asynchronous Warning" }], | ||
}) | ||
}) | ||
|
||
// This adds > 6 seconds to the tests! Only orta should be forced into that. | ||
if (process.env["USER"] === "orta") { | ||
it("can execute async/await scheduled functions", async () => { | ||
// this test takes *forever* because of babel-polyfill being required | ||
const context = await setupDangerfileContext() | ||
const runtime = await createDangerfileRuntimeEnvironment(context) | ||
const results = await runDangerfileEnvironment(resolve(fixtures, "__DangerfileAsync.js"), runtime) | ||
expect(results.warnings).toEqual([ | ||
{ | ||
message: "Async Function", | ||
}, | ||
{ | ||
message: "After Async Function", | ||
}, | ||
]) | ||
}) | ||
} | ||
|
||
it("can schedule callback-based promised", async () => { | ||
const context = await setupDangerfileContext() | ||
const runtime = await createDangerfileRuntimeEnvironment(context) | ||
const results = await runDangerfileEnvironment(resolve(fixtures, "__DangerfileCallback.js"), runtime) | ||
expect(results.warnings).toEqual([ | ||
{ | ||
message: "Scheduled a callback", | ||
}, | ||
]) | ||
}) | ||
|
||
it("can handle TypeScript based Dangerfiles", async () => { | ||
const context = await setupDangerfileContext() | ||
const runtime = await createDangerfileRuntimeEnvironment(context) | ||
const results = await runDangerfileEnvironment(resolve(fixtures, "__DangerfileTypeScript.ts"), runtime) | ||
expect(results.messages).toEqual([ | ||
{ | ||
message: "Honey, we got Types", | ||
}, | ||
]) | ||
}) | ||
|
||
it("can handle a plugin (which is already used in Danger)", async () => { | ||
const context = await setupDangerfileContext() | ||
const runtime = await createDangerfileRuntimeEnvironment(context) | ||
const results = await runDangerfileEnvironment(resolve(fixtures, "__DangerfilePlugin.js"), runtime) | ||
|
||
expect(results.fails[0].message).toContain("@types dependencies were added to package.json") | ||
}) | ||
}) | ||
|
||
describe("cleaning Dangerfiles", () => { | ||
it("also handles typescript style imports", () => { | ||
const before = ` | ||
import { danger, warn, fail, message } from 'danger' | ||
import { danger, warn, fail, message } from "danger" | ||
import { danger, warn, fail, message } from "danger"; | ||
import danger from "danger" | ||
import danger from 'danger' | ||
import danger from 'danger'; | ||
` | ||
const after = ` | ||
// Removed import | ||
// Removed import | ||
// Removed import | ||
// Removed import | ||
// Removed import | ||
// Removed import | ||
` | ||
expect(cleanDangerfile(before)).toEqual(after) | ||
}) | ||
|
||
it("also handles require style imports", () => { | ||
const before = ` | ||
const { danger, warn, fail, message } = require('danger') | ||
var { danger, warn, fail, message } = require("danger") | ||
let { danger, warn, fail, message } = require('danger'); | ||
` | ||
const after = ` | ||
// Removed require | ||
// Removed require | ||
// Removed require | ||
` | ||
expect(cleanDangerfile(before)).toEqual(after) | ||
}) | ||
}) |
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