From df31b7722e10b5ed20e14eff855cb6c907018ca2 Mon Sep 17 00:00:00 2001 From: Emanuele Libralato Date: Tue, 9 Jul 2019 16:12:14 +0200 Subject: [PATCH] feat: added glob and test names to dev/test commands --- docs/reference/commands.md | 9 +++- garden-service/src/commands/dev.ts | 16 ++++++-- garden-service/src/commands/test.ts | 10 +++-- garden-service/src/tasks/test.ts | 14 +++++-- .../data/test-project-a/module-a/garden.yml | 2 + .../data/test-project-a/module-c/garden.yml | 2 + garden-service/test/unit/src/commands/test.ts | 41 +++++++++++++++++++ 7 files changed, 80 insertions(+), 14 deletions(-) diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 1edf712304..c26aa3b7c2 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -189,9 +189,11 @@ as you modify the code. Examples: garden dev - garden dev --hot-reload=foo-service # enable hot reloading for foo-service garden dev --hot=foo-service,bar-service # enable hot reloading for foo-service and bar-service garden dev --hot=* # enable hot reloading for all compatible services + garden dev --skip-tests=foo-service # skip running any tests + garden dev --name integ # run all tests with the name 'integ' in the project + garden test --name integ* # run all tests with the name starting with 'integ' in the project ##### Usage @@ -202,6 +204,8 @@ Examples: | Argument | Alias | Type | Description | | -------- | ----- | ---- | ----------- | | `--hot-reload` | `-hot` | array:string | The name(s) of the service(s) to deploy with hot reloading enabled. Use comma as a separator to specify multiple services. Use * to deploy all services with hot reloading enabled (ignores services belonging to modules that don't support or haven't configured hot reloading). + | `--skip-tests` | | boolean | Disable running the tests + | `--test-names` | `-tn` | array:string | The name(s) of the module(s) to test (skip to test all modules). Accepts glob patterns (e.g. integ* would run both 'integ' and 'integration') ### garden exec @@ -696,6 +700,7 @@ Examples: garden test # run all tests in the project garden test my-module # run all tests in the my-module module garden test --name integ # run all tests with the name 'integ' in the project + garden test --name integ* # run all tests with the name starting with 'integ' in the project garden test --force # force tests to be re-run, even if they've already run successfully garden test --watch # watch for changes to code @@ -713,7 +718,7 @@ Examples: | Argument | Alias | Type | Description | | -------- | ----- | ---- | ----------- | - | `--name` | `-n` | string | Only run tests with the specfied name (e.g. unit or integ). + | `--name` | `-n` | string | Only run tests with the specfied name (e.g. unit or integ). Accepts glob patterns (e.g. integ* would run both 'integ' and 'integration') | `--force` | `-f` | boolean | Force re-test of module(s). | `--force-build` | | boolean | Force rebuild of module(s). | `--watch` | `-w` | boolean | Watch for changes in module(s) and auto-test. diff --git a/garden-service/src/commands/dev.ts b/garden-service/src/commands/dev.ts index 5e01c200b1..12a03daaf3 100644 --- a/garden-service/src/commands/dev.ts +++ b/garden-service/src/commands/dev.ts @@ -49,7 +49,12 @@ const devOpts = { alias: "hot", }), "skip-tests": new BooleanParameter({ - help: "Disable running the tests", + help: "Disable running the tests.", + }), + "test-names": new StringsParameter({ + help: "The name(s) of the module(s) to test (skip to test all modules). " + + "Accepts glob patterns (e.g. integ* would run both 'integ' and 'integration').", + alias: "tn", }), } @@ -72,9 +77,11 @@ export class DevCommand extends Command { Examples: garden dev - garden dev --skip-tests=foo-service # enable hot reloading for foo-service garden dev --hot=foo-service,bar-service # enable hot reloading for foo-service and bar-service garden dev --hot=* # enable hot reloading for all compatible services + garden dev --skip-tests= # skip running any tests + garden dev --name integ # run all tests with the name 'integ' in the project + garden test --name integ* # run all tests with the name starting with 'integ' in the project ` options = devOpts @@ -134,12 +141,13 @@ export class DevCommand extends Command { : [module] if (!opts["skip-tests"]) { + const filterNames = opts["test-names"] tasks.push(...flatten( - await Bluebird.map(testModules, m => getTestTasks({ garden, log, module: m, graph: updatedGraph })), + await Bluebird.map( + testModules, m => getTestTasks({ garden, log, module: m, graph: updatedGraph, filterNames })), )) } - tasks.push(...await getDependantTasksForModule({ garden, log, diff --git a/garden-service/src/commands/test.ts b/garden-service/src/commands/test.ts index 814a6ac90d..ce7d4a45ca 100644 --- a/garden-service/src/commands/test.ts +++ b/garden-service/src/commands/test.ts @@ -36,7 +36,8 @@ const testArgs = { const testOpts = { "name": new StringOption({ - help: "Only run tests with the specfied name (e.g. unit or integ).", + help: "Only run tests with the specfied name (e.g. unit or integ). " + + "Accepts glob patterns (e.g. integ* would run both 'integ' and 'integration')", alias: "n", }), "force": new BooleanParameter({ help: "Force re-test of module(s).", alias: "f" }), @@ -67,6 +68,7 @@ export class TestCommand extends Command { garden test # run all tests in the project garden test my-module # run all tests in the my-module module garden test --name integ # run all tests with the name 'integ' in the project + garden test --name integ* # run all tests with the name starting with 'integ' in the project garden test --force # force tests to be re-run, even if they've already run successfully garden test --watch # watch for changes to code ` @@ -102,7 +104,7 @@ export class TestCommand extends Command { const actions = await garden.getActionHelper() await actions.prepareEnvironment({ log }) - const name = opts.name + const filterNames = opts.name ? [opts.name] : [] const force = opts.force const forceBuild = opts["force-build"] @@ -114,13 +116,13 @@ export class TestCommand extends Command { modules, watch: opts.watch, handler: async (updatedGraph, module) => getTestTasks({ - garden, log, graph: updatedGraph, module, name, force, forceBuild, + garden, log, graph: updatedGraph, module, filterNames, force, forceBuild, }), changeHandler: async (updatedGraph, module) => { const modulesToProcess = await updatedGraph.withDependantModules([module]) return flatten(await Bluebird.map( modulesToProcess, - m => getTestTasks({ garden, log, graph: updatedGraph, module: m, name, force, forceBuild }))) + m => getTestTasks({ garden, log, graph: updatedGraph, module: m, filterNames, force, forceBuild }))) }, }) diff --git a/garden-service/src/tasks/test.ts b/garden-service/src/tasks/test.ts index 12dc316f91..f1e1fff98b 100644 --- a/garden-service/src/tasks/test.ts +++ b/garden-service/src/tasks/test.ts @@ -20,7 +20,8 @@ import { LogEntry } from "../logger/log-entry" import { ConfigGraph } from "../config-graph" import { makeTestTaskName } from "./helpers" import { BuildTask } from "./build" - +import minimatch = require("minimatch") +import { find } from "lodash" class TestError extends Error { toString() { return this.message @@ -164,18 +165,23 @@ export class TestTask extends BaseTask { } export async function getTestTasks( - { garden, log, graph, module, name, force = false, forceBuild = false }: + { garden, log, graph, module, filterNames, force = false, forceBuild = false }: { garden: Garden, log: LogEntry, graph: ConfigGraph, module: Module, - name?: string, + filterNames?: string[], force?: boolean, forceBuild?: boolean, }, ) { - const configs = module.testConfigs.filter(test => !name || test.name === name) + + // If there are no filters we return the test otherwise + // we check if the test name matches against the filterNames array + const configs = module.testConfigs.filter( + test => !filterNames || filterNames.length === 0 + || find(filterNames, (n: string) => minimatch(test.name, n))) return Bluebird.map(configs, test => TestTask.factory({ garden, diff --git a/garden-service/test/unit/data/test-project-a/module-a/garden.yml b/garden-service/test/unit/data/test-project-a/module-a/garden.yml index 5ca3f64a6b..92469aba01 100644 --- a/garden-service/test/unit/data/test-project-a/module-a/garden.yml +++ b/garden-service/test/unit/data/test-project-a/module-a/garden.yml @@ -8,6 +8,8 @@ module: tests: - name: unit command: [echo, OK] + - name: integration + command: [echo, OK] tasks: - name: task-a command: [echo, OK] diff --git a/garden-service/test/unit/data/test-project-a/module-c/garden.yml b/garden-service/test/unit/data/test-project-a/module-c/garden.yml index 84ffcaaf3e..438d8073bc 100644 --- a/garden-service/test/unit/data/test-project-a/module-c/garden.yml +++ b/garden-service/test/unit/data/test-project-a/module-c/garden.yml @@ -9,6 +9,8 @@ module: tests: - name: unit command: [echo, OK] + - name: integ + command: [echo, OK] tasks: - name: task-c command: [echo, OK] diff --git a/garden-service/test/unit/src/commands/test.ts b/garden-service/test/unit/src/commands/test.ts index b467acbc1d..49634392de 100644 --- a/garden-service/test/unit/src/commands/test.ts +++ b/garden-service/test/unit/src/commands/test.ts @@ -68,4 +68,45 @@ describe("commands.test", () => { }, })).to.be.true }) + + it("should only run integration tests if the option 'name' is specified with a glob", async () => { + const garden = await makeTestGardenA() + const log = garden.log + const command = new TestCommand() + + const { result } = await command.action({ + garden, + log, + headerLog: log, + footerLog: log, + args: { modules: ["module-a"] }, + opts: withDefaultGlobalOpts({ "name": "int*", "force": true, "force-build": true, "watch": false }), + }) + + expect(isSubset(taskResultOutputs(result!), { + "build.module-a": { + fresh: true, + buildLog: "A", + }, + "test.module-a.integration": { + success: true, + output: "OK", + }, + "test.module-c.integ": { + success: true, + output: "OK", + }, + })).to.be.true + + expect(isSubset(taskResultOutputs(result!), { + "test.module-a.unit": { + success: true, + output: "OK", + }, + "test.module-c.unit": { + success: true, + output: "OK", + }, + })).to.be.false + }) })