Skip to content

Commit

Permalink
Fixded a bug in circular dep detection logic.
Browse files Browse the repository at this point in the history
Also moved util.js to util/index.js, along with some misc cleanup.
  • Loading branch information
thsig committed May 17, 2018
1 parent a6b8831 commit d98fda5
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 42 deletions.
2 changes: 1 addition & 1 deletion src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ import { Task } from "./types/task"
import {
LocalConfigStore,
} from "./config-store"
import { detectCircularDependencies } from "./algo"
import { detectCircularDependencies } from "./util/detectCycles"

export interface ModuleMap<T extends Module> {
[key: string]: T
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/kubernetes/kubectl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class Kubectl {
args: string[],
{ data, ignoreError = false, silent = true, timeout = KUBECTL_DEFAULT_TIMEOUT }: KubectlParams = {},
): Promise<KubectlOutput> {
// TODO: use the spawn helper from util.ts
// TODO: use the spawn helper from index.ts
const logger = getLogger()
const out: KubectlOutput = {
code: 0,
Expand Down
14 changes: 8 additions & 6 deletions src/algo.ts → src/util/detectCycles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*/

import { forEach, get, isEqual, join, set, uniqWith } from "lodash"
import { Module, ModuleConfig } from "./types/module"
import { ConfigurationError } from "./exceptions"
import { Module, ModuleConfig } from "../types/module"
import { ConfigurationError } from "../exceptions"

type Cycle = string[]
export type Cycle = string[]

/*
Implements a variation on the Floyd-Warshall algorithm to compute minimal cycles.
Expand Down Expand Up @@ -65,7 +65,7 @@ export async function detectCircularDependencies(modules: Module[], serviceNames
}
}

function detectCycles(graph, vertices: string[]): Cycle[] {
export function detectCycles(graph, vertices: string[]): Cycle[] {
// Compute shortest paths
for (const k of vertices) {
for (const i of vertices) {
Expand All @@ -88,10 +88,12 @@ function detectCycles(graph, vertices: string[]): Cycle[] {
cycle.push(nextInCycle)
nextInCycle = next(graph, nextInCycle, v)
}
return cycle.sort()
return cycle
})

return uniqWith(cycles, isEqual)
return uniqWith(
cycles, // The concat calls below are to prevent in-place sorting.
(c1, c2) => isEqual(c1.concat().sort(), c2.concat().sort()))
}

function distance(graph, source, destination): number {
Expand Down
6 changes: 3 additions & 3 deletions src/util.ts → src/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ import * as inquirer from "inquirer"
import { spawn as _spawn } from "child_process"
import { existsSync, readFileSync, writeFileSync } from "fs"
import { join } from "path"
import { getLogger, RootLogNode } from "./logger"
import { getLogger, RootLogNode } from "../logger/index"
import {
TimeoutError,
GardenError,
} from "./exceptions"
} from "../exceptions"
import { PassThrough } from "stream"
import { isArray, isPlainObject, extend, mapValues, pickBy } from "lodash"
import highlight from "cli-highlight"
import chalk from "chalk"
import { FancyConsoleWriter } from "./logger/writers"
import { FancyConsoleWriter } from "../logger/writers"
import hasAnsi = require("has-ansi")

// shim to allow async generator functions
Expand Down
9 changes: 0 additions & 9 deletions test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,6 @@ export async function expectError(fn: Function, typeOrCallback: string | ((err:
}
}

export async function expectNoError(fn: Function) {
try {
await fn()
} catch (err) {
throw new Error(`Did not expect an error, but got ${err}`)
}
return
}

export function taskResultOutputs(results: TaskResults) {
return mapValues(results, r => r.output)
}
20 changes: 0 additions & 20 deletions test/src/algo.ts

This file was deleted.

42 changes: 40 additions & 2 deletions test/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { scanDirectory } from "../../src/util"
import { join } from "path"
import { expect } from "chai"
import { join } from "path"
import { expectError, makeTestGarden } from "../helpers"
import { scanDirectory } from "../../src/util"
import { detectCycles } from "../../src/util/detectCycles"

describe("util", () => {
describe("scanDirectory", () => {
Expand Down Expand Up @@ -32,5 +34,41 @@ describe("util", () => {

expect(count).to.eq(3)
})

it("should throw an exception when circular dependencies are present", async () => {
const circularProjectRoot = join(__dirname, "..", "data", "test-project-circular-deps")
const garden = await makeTestGarden(circularProjectRoot)
await expectError(
async () => await garden.scanModules(),
"configuration")
})

it("should not throw an exception when no circular dependencies are present", async () => {
const nonCircularProjectRoot = join(__dirname, "..", "data", "test-project-b")
const garden = await makeTestGarden(nonCircularProjectRoot)
expect(async () => { await garden.scanModules() }).to.not.throw()
})
})

describe("detectCycles", () => {

it("should detect self-to-self cycles", () => {
const cycles = detectCycles({
a: {a: {distance: 1, next: "a"}},
}, ["a"])

expect(cycles).to.deep.eq([["a"]])
})

it("should preserve dependency order when returning cycles", () => {
const cycles = detectCycles({
foo: {bar: {distance: 1, next: "bar"}},
bar: {baz: {distance: 1, next: "baz"}},
baz: {foo: {distance: 1, next: "foo"}},
}, ["foo", "bar", "baz"])

expect(cycles).to.deep.eq([["foo", "bar", "baz"]])
})

})
})

0 comments on commit d98fda5

Please sign in to comment.