Skip to content

Commit

Permalink
Adds support for mutliple danger file runs in a single execution
Browse files Browse the repository at this point in the history
  • Loading branch information
orta committed May 13, 2018
1 parent 64d7ae0 commit a829ffa
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 120 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@

## Master

* Allows the synchronous execution of multiple dangerfiles in one single "danger run".

Not a particularly useful feature for Danger-JS, but it means Peril can combine many runs into a single execution
unit. This means people only get 1 message. [@orta][]

# 3.6.6

* Updates vm2 to be an npm published version [@orta][]
Expand Down
2 changes: 1 addition & 1 deletion source/commands/danger-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const run = async (jsonString: string) => {
const context = await jsonToContext(jsonString, program)
runtimeEnv = await inline.createDangerfileRuntimeEnvironment(context)
d(`Evaluating ${dangerFile}`)
await inline.runDangerfileEnvironment(dangerFile, undefined, runtimeEnv)
await inline.runDangerfileEnvironment([dangerFile], [undefined], runtimeEnv)
}

// Wait till the end of the process to print out the results. Will
Expand Down
7 changes: 4 additions & 3 deletions source/dsl/DangerResults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,10 @@ export function sortResults(results: DangerResults): DangerResults {
}
}

export function isEmptyResults(results: DangerResults): boolean {
return [...results.fails, ...results.warnings, ...results.messages, ...results.markdowns].length === 0
}
export const emptyResults = (): DangerResults => ({ fails: [], markdowns: [], warnings: [], messages: [] })

export const isEmptyResults = (results: DangerResults): boolean =>
[...results.fails, ...results.warnings, ...results.messages, ...results.markdowns].length === 0

export function resultsIntoInlineResults(results: DangerResults): DangerInlineResults[] {
// Here we iterate through all keys ("fails", "warnings", "messages", "markdowns") and for each violation
Expand Down
6 changes: 3 additions & 3 deletions source/runner/Executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class Executor {
// If an eval of the Dangerfile fails, we should generate a
// message that can go back to the CI
try {
results = await this.runner.runDangerfileEnvironment(file, undefined, runtime)
results = await this.runner.runDangerfileEnvironment([file], [undefined], runtime)
} catch (error) {
results = this.resultsForError(error)
}
Expand Down Expand Up @@ -352,12 +352,12 @@ export class Executor {
}

/**
* Takes an error (maybe a bad eval) and provides a DangerResults compatible object3ehguh.l;/////////////
* Takes an error (maybe a bad eval) and provides a DangerResults compatible object
* @param error Any JS error
*/
resultsForError(error: Error) {
// Need a failing error, otherwise it won't fail CI.
console.error(chalk.red("Danger has errored"))
console.error(chalk.red("Danger has failed to run"))
console.error(error)
return {
fails: [{ message: "Running your Dangerfile has Failed" }],
Expand Down
1 change: 1 addition & 0 deletions source/runner/_tests/fixtures/__DangerfileTypeScript.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-ignore
/* tslint-disable */

// This doesn't exist in JS-world
Expand Down
64 changes: 49 additions & 15 deletions source/runner/runners/_tests/vm2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileEmpty.js"),
[resolve(fixtures, "__DangerfileEmpty.js")],
undefined,
runtime
)
Expand All @@ -91,7 +91,7 @@ runners.forEach(run => {
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)

const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileFullMessages.js"),
[resolve(fixtures, "__DangerfileFullMessages.js")],
undefined,
runtime
)
Expand All @@ -108,7 +108,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileBadSyntax.js"),
[resolve(fixtures, "__DangerfileBadSyntax.js")],
undefined,
runtime
)
Expand All @@ -121,7 +121,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileImportRelative.js"),
[resolve(fixtures, "__DangerfileImportRelative.js")],
undefined,
runtime
)
Expand All @@ -131,7 +131,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileScheduled.js"),
[resolve(fixtures, "__DangerfileScheduled.js")],
undefined,
runtime
)
Expand All @@ -147,7 +147,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileMultiScheduled.js"),
[resolve(fixtures, "__DangerfileMultiScheduled.js")],
undefined,
runtime
)
Expand All @@ -163,7 +163,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileAsync.ts"),
[resolve(fixtures, "__DangerfileAsync.ts")],
undefined,
runtime
)
Expand All @@ -182,7 +182,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileAsync.js"),
[resolve(fixtures, "__DangerfileAsync.js")],
undefined,
runtime
)
Expand All @@ -201,7 +201,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileAsync.ts"),
[resolve(fixtures, "__DangerfileAsync.ts")],
undefined,
runtime
)
Expand All @@ -219,7 +219,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileCallback.js"),
[resolve(fixtures, "__DangerfileCallback.js")],
undefined,
runtime
)
Expand All @@ -234,7 +234,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileTypeScript.ts"),
[resolve(fixtures, "__DangerfileTypeScript.ts")],
undefined,
runtime
)
Expand All @@ -249,7 +249,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfilePlugin.js"),
[resolve(fixtures, "__DangerfilePlugin.js")],
undefined,
runtime
)
Expand All @@ -261,7 +261,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileThrows.js"),
[resolve(fixtures, "__DangerfileThrows.js")],
undefined,
runtime
)
Expand All @@ -274,7 +274,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileDefaultExport.js"),
[resolve(fixtures, "__DangerfileDefaultExport.js")],
undefined,
runtime
)
Expand All @@ -290,7 +290,7 @@ runners.forEach(run => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
resolve(fixtures, "__DangerfileDefaultExportAsync.js"),
[resolve(fixtures, "__DangerfileDefaultExportAsync.js")],
undefined,
runtime
)
Expand All @@ -301,6 +301,40 @@ runners.forEach(run => {
warnings: [{ message: "Asynchronous Warning" }],
})
})

it("handles running multiple local files", async () => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
[resolve(fixtures, "__DangerfileTypeScript.ts"), resolve(fixtures, "__DangerfileAsync.ts")],
undefined,
runtime
)

expect(results).toEqual({
fails: [],
markdowns: [],
messages: [{ message: "Honey, we got Types" }],
warnings: [{ message: "Async Function" }, { message: "After Async Function" }],
})
})

it("handles running multiple dangerfiles with passed in content", async () => {
const context = await setupDangerfileContext()
const runtime = await exec.runner.createDangerfileRuntimeEnvironment(context)
const results = await exec.runner.runDangerfileEnvironment(
[resolve(fixtures, "__MadeUpDangerfileOne.ts"), resolve(fixtures, "__MadeUpDangerfileTwo.ts")],
["markdown('hello')", "markdown('hello2')"],
runtime
)

expect(results).toEqual({
fails: [],
markdowns: [{ message: "hello" }, { message: "hello2" }],
messages: [],
warnings: [],
})
})
})
})
})
116 changes: 65 additions & 51 deletions source/runner/runners/inline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ export async function createDangerfileRuntimeEnvironment(dangerfileContext: Dang
return dangerfileContext
}

const runAllScheduledTasks = async (results: DangerRuntimeContainer) => {
if (results.scheduled) {
d(`Scheduler waiting on: ${results.scheduled.length} tasks`)
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()
})
)
d(`Finished scheduled tasks`)
}
}

/**
* Executes a Dangerfile at a specific path, with a context.
* The values inside a Danger context are applied as globals to the Dangerfiles runtime.
Expand All @@ -34,12 +53,12 @@ export async function createDangerfileRuntimeEnvironment(dangerfileContext: Dang
* @param {any | undefined} injectedObjectToExport an optional object for passing into default exports
* @returns {DangerResults} the results of the run
*/
export async function runDangerfileEnvironment(
filename: string,
originalContents: string | undefined,
export const runDangerfileEnvironment = async (
filenames: string[],
originalContents: (string | undefined)[],
environment: DangerContext,
injectedObjectToExport: any | undefined = undefined
): Promise<DangerResults> {
injectedObjectToExport?: any
): Promise<DangerResults> => {
// We need to change the local runtime to support running JavaScript
// and TypeScript through babel first. This is a simple implementation
// and if we need more nuance, then we can look at other options
Expand All @@ -58,58 +77,53 @@ export async function runDangerfileEnvironment(
require.extensions[".js"] = customModuleHandler
require.extensions[".jsx"] = customModuleHandler

// Require our dangerfile
originalContents = originalContents || fs.readFileSync(filename, "utf8")
let content = cleanDangerfile(originalContents)
let compiled = compile(content, filename)

try {
// Move all the DSL attributes into the global scope
for (let key in environment) {
if (environment.hasOwnProperty(key)) {
let element = environment[key]
global[key] = element
// Loop through all files and their potential contents, they edit
// results inside the env, so no need to keep track ourselves

for (const filename of filenames) {
const index = filenames.indexOf(filename)
const originalContent = (originalContents && originalContents[index]) || fs.readFileSync(filename, "utf8")
let content = cleanDangerfile(originalContent)
let compiled = compile(content, filename)

try {
// Move all the DSL attributes into the global scope
for (let key in environment) {
if (environment.hasOwnProperty(key)) {
let element = environment[key]
global[key] = element
}
}
}

d("Started parsing Dangerfile: ", filename)
const optionalExport = _require(compiled, filename, {})
d("Started parsing Dangerfile: ", filename)
const optionalExport = _require(compiled, filename, {})

if (typeof optionalExport.default === "function") {
d("Running default export from Dangerfile", filename)
await optionalExport.default(injectedObjectToExport)
if (typeof optionalExport.default === "function") {
d("Running default export from Dangerfile", filename)
await optionalExport.default(injectedObjectToExport)
}
d("Finished running dangerfile: ", filename)
// Don't stop all current async code from breaking,
// however new code (without Peril support) can run
// without the scheduler
await runAllScheduledTasks(environment.results)
} catch (error) {
console.error("Unable to evaluate the Dangerfile\n", error)
d("Got a parse error: ", error)

// Call the internal functions to fail the build
const errorResults = resultsForCaughtError(filename, content, error)
environment.markdown(errorResults.markdowns[0].message)
environment.fail(errorResults.fails[0].message)
}
d("Finished running dangerfile: ", filename)
// Don't stop all current async code from breaking,
// however new code (without Peril support) can run
// without the scheduler
await runAllScheduledTasks(environment.results)

return environment.results
} catch (error) {
console.error("Unable to evaluate the Dangerfile\n", error)
d("Got a parse error: ", error)
environment.results = resultsForCaughtError(filename, content, error)
return environment.results
}
}

const runAllScheduledTasks = async (results: DangerRuntimeContainer) => {
if (results.scheduled) {
d(`Scheduler waiting on: ${results.scheduled.length} tasks`)
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()
})
)
d(`Finished scheduled tasks`)
const results = environment.results
return {
fails: results.fails,
warnings: results.warnings,
messages: results.messages,
markdowns: results.markdowns,
}
}

Expand Down

0 comments on commit a829ffa

Please sign in to comment.