Skip to content
This repository was archived by the owner on Jun 29, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 217 additions & 24 deletions .narval.yml

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Removed
### BREAKING CHANGES

## [2.1.0] - 2018-08-17
### Added
- Add logLevel option.
- Add describe property to suites. Log the description when suite starts.

## [2.0.0] - 2018-08-12
### Added
- Add support for "include" yaml feature, that allows to split configuration into many partial files.
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ![Narval Logo][narval-logo-image]

Multi test suites runner for Node.js packages. Powered by [Docker][docker-url].
Multi test suites runner for Node.js packages. Define your suites including dependant services, Narval will start all using Docker, isolating each suite execution.

[![Build status][travisci-image]][travisci-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Quality Gate][quality-gate-image]][quality-gate-url] [![js-standard-style][standard-image]][standard-url]

Expand Down Expand Up @@ -187,6 +187,7 @@ docker-containers:
`<Object>`. Object containing the test suite configuration.

* `name` `<String>`. Reference for the test suite.
* `describe` `<String>`. Description of the suite. Printed in logs when suite starts.
* `before` `<Object>`. [before object](#before) containing configuration for commands that will be executed before running the test suite. Useful to clean or prepare your environment.
* `services` `<Array>` of [service objects](#service). Defines services to be started before running the tests.
* `test` `<Object>`. [test object](#test) containing configuration of the test to be runned by this suite.
Expand Down Expand Up @@ -450,6 +451,7 @@ Available options are:

option | description | alias
--- | --- | ---
`--logLevel` | Define log level. Available levels are "log", "trace", "debug", "info", "warn" and "error". Default is "info" | `--log`
`--standard` | Run only Standard linter | `-s`
`--fix` | Fix Standard errors | `-f`
`--type <type>` | Run only test suites of provided `<type>` |
Expand All @@ -466,6 +468,9 @@ npm test -- --standard
npm test -- --type=integration
# Run all test suites of type "integration" defined in the configuration

npm test -- --logLevel=debug
# Run tests, only print logs with level debug or upper.

npm test -- --type=integration --local
# Run locally all test suites of type "integration"

Expand Down Expand Up @@ -693,13 +698,15 @@ docker-containers:
suites:
unit:
- name: unit
describe: Unitary tests
test:
specs: test/unit
coverage:
config:
dir: .coverage/unit
end-to-end:
- name: books-api
describe: Books api should work and save data to mongodb.
before:
docker:
down-volumes: true
Expand Down Expand Up @@ -733,6 +740,7 @@ suites:
from: api-server
integration:
- name: logs
describe: Books api logs should work and print logs when an api request is received.
services:
- name: mongodb
docker:
Expand Down Expand Up @@ -774,6 +782,7 @@ suites:
coverage:
enabled: false
- name: commands
describe: Books api command should work and write all books to a file in json format.
services:
- name: mongodb
docker:
Expand Down Expand Up @@ -809,6 +818,7 @@ suites:
api_port: 4000
coverage:
enabled: false

```

> NOTE: There are more files with many other configurations that may be useful as examples at the [integration tests folder of this repository][integration-tests-config-url]. All these Narval configuration files are used for testing Narval itself, and most of them are runned over the ["foo api package" in the integration tests folder][integration-tests-foo-package-url].
Expand Down
44 changes: 27 additions & 17 deletions lib/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const baseDockerProcessOptions = function () {
return {
cwd: paths.docker(),
encoding: 'utf8',
stdio: [0, 1, 2],
shell: true,
windowsHide: true
}
Expand Down Expand Up @@ -126,24 +125,35 @@ const processOptions = function (extraOptions) {
return (Object.assign({}, baseDockerProcessOptions(), extraOptions))
}

const runComposeSync = function (command, options) {
return new Promise((resolve, reject) => {
logs.runningComposeCommand({
command: command
const runComposeSync = function (command, extraOptions) {
return options.get()
.then(opts => {
return new Promise((resolve, reject) => {
const stdio = opts.logLevel < 1 ? [0, 1, 2] : [0, 'pipe', 'pipe']
logs.runningComposeCommand({
command: command
})
logs.dockerComposeLogs({
command: command
})

try {
processes.execSync(`docker-compose -f docker-compose.json ${command}`, processOptions({
...extraOptions,
stdio
}))
resolve()
} catch (error) {
reject(Boom.badImplementation(logs.composeCommandFailed({
command: command
}, false)))
}
})
})
try {
processes.execSync(`docker-compose -f docker-compose.json ${command}`, processOptions(options))
resolve()
} catch (error) {
reject(Boom.badImplementation(logs.composeCommandFailed({
command: command
}, false)))
}
})
}

module.exports = {
run: run,
runBefore: runBefore,
runComposeSync: runComposeSync
run,
runBefore,
runComposeSync
}
19 changes: 17 additions & 2 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ const SuiteResolver = function (suiteData, suiteTypeName, options, suitesByType)
return name
}

const describe = () => {
return suiteData.describe || ''
}

const typeName = function () {
return suiteTypeName
}
Expand All @@ -209,11 +213,21 @@ const SuiteResolver = function (suiteData, suiteTypeName, options, suitesByType)
let baseConfig = {
dir: `.coverage/${suiteTypeName}/${name}`
}
const config = Object.assign({}, baseConfig, suiteData.coverage && suiteData.coverage.config)
return istanbulArguments(config)
const userConfig = (suiteData.coverage && suiteData.coverage.config) || {}
if (options.logLevel > 3 && !userConfig.print) {
userConfig.print = 'none'
}
return istanbulArguments({
...baseConfig,
...userConfig
})
}

const mochaArgs = function () {
suiteData.test.config = suiteData.test.config || {}
if (options.logLevel > 3 && !suiteData.test.config.reporter) {
suiteData.test.config.reporter = 'mocha-silent-reporter'
}
return mochaArguments(suiteData.test.config, suiteData.test.specs)
}

Expand Down Expand Up @@ -442,6 +456,7 @@ const SuiteResolver = function (suiteData, suiteTypeName, options, suitesByType)
return {
typeName: typeName,
name: getName,
describe: describe,
hasToRun: hasToRun,
isDocker: isDocker,
istanbulArguments: istanbulArgs,
Expand Down
2 changes: 2 additions & 0 deletions lib/docker.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const baseDockerCompose = require('./templates/docker-compose.json')
const states = require('./states')
const utils = require('./utils')
const commands = require('./commands')
const logs = require('./logs')

const DOCKER_RESOURCES_PATH = 'docker-resources'
const INSTALL_RESOURCES_PATH = 'install-resources'
Expand Down Expand Up @@ -179,6 +180,7 @@ const downVolumes = function () {
if (!states.get('docker-executed')) {
return Promise.resolve()
}
logs.dockerVolumesDown()
return config.allComposeEnvVars()
.then((vars) => {
return commands.runComposeSync('down --volumes', {
Expand Down
16 changes: 11 additions & 5 deletions lib/logs.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const _ = require('lodash')
const handlebars = require('handlebars')

const tracer = require('./tracer')
const utils = require('./utils')

handlebars.registerHelper('comma-separated', function (arr) {
arr = _.isString(arr) ? [arr] : arr
Expand All @@ -12,7 +13,7 @@ handlebars.registerHelper('comma-separated', function (arr) {

const SERVICE_LOGS = {
skip: ['Skipping {{{data.fullName}}}', 'warn'],
startRun: ['Running {{{data.fullName}}}', 'info'],
startRun: [`Running {{{data.fullName}}}:${utils.NO_COLOR_SEP} {{{data.describe}}}`, 'info'],
finishOk: ['Execution of {{{data.fullName}}} finished OK', 'info'],
finishError: ['Error running {{{data.fullName}}}', 'error'],
beforeCommand: ['Executing before command "{{{data.command}}}"', 'debug'],
Expand All @@ -39,13 +40,13 @@ const SERVICE_LOGS = {

const LOGS = {
skipAllSuites: ['Skipping all test suites', 'warn'],
waitingOn: ['Waiting until "{{data.resources}}" is available.{{{data.customConfig}}}', 'debug'],
waitingOn: ['Waiting until "{{data.resources}}" is available.', 'debug'],
waitTimeOut: ['Wait timed out. "{{data.resources}}" is not available.', 'error'],
waitFinish: ['Wait finished. "{{data.resources}}" is available.', 'debug'],
waitConfig: [' Applying custom config: {{{data.config}}}'],
waitConfig: [`Waiting config =>${utils.NO_COLOR_SEP} {{{data.config}}}`, 'trace'],
errorRunningCommand: ['Error trying to run command. {{{data.message}}}', 'error'],
errorRunningCommandCode: ['Error running command. Exit code {{data.code}}', 'error'],
runningComposeCommand: ['Running Docker command "docker-compose {{{data.command}}}"', 'log'],
runningComposeCommand: ['Running Docker command "docker-compose {{{data.command}}}"', 'trace'],
composeCommandFailed: ['Docker compose command "{{{data.command}}}" failed', 'error'],
configNotFound: ['Config file {{data.filePath}} not found', 'warn'],
serviceNotFound: ['The service {{data.name}} was not found', 'warn'],
Expand All @@ -58,7 +59,12 @@ const LOGS = {
standardOk: ['Standard finished OK', 'info'],
skipStandard: ['Skipping Standard', 'warn'],
runningSuiteType: ['Running suites of type "{{data.type}}"', 'info'],
skipSuiteType: ['Skipping suites of type "{{data.type}}"', 'warn']
skipSuiteType: ['Skipping suites of type "{{data.type}}"', 'warn'],
serviceLog: [`"{{data.service}}" log =>${utils.NO_COLOR_SEP} {{{data.log}}}`, 'trace'],
dockerComposeLogs: [`Docker logs =>`, 'log'],
dockerVolumesDown: ['Unmounting docker-compose volumes', 'debug'],
dockerDown: ['Stopping docker-compose', 'debug'],
dockerUp: ['Starting docker-compose', 'debug']
}

const Log = function (message, level, preData = {}) {
Expand Down
17 changes: 15 additions & 2 deletions lib/options.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
'use strict'

const commander = require('commander')
const Boom = require('boom')
const Promise = require('bluebird')
const tracer = require('tracer')

const states = require('./states')

const getCommandOptions = function () {
const LOG_LEVELS = ['log', 'trace', 'debug', 'info', 'warn', 'error']

const getCommandOptions = () => {
return commander
.option('-s, --standard', 'Run standard')
.option('--type <type>', 'Run only all suites of an specific type')
Expand All @@ -14,6 +18,7 @@ const getCommandOptions = function () {
.option('-f, --fix', 'Fix standard')
.option('-b, --build', 'Build docker images')
.option('--shell <shell>', 'Custom shell used for running commands without Docker')
.option('--log --logLevel <log>', 'Log level')
.parse(process.argv)
}

Expand All @@ -22,6 +27,7 @@ const get = function () {
if (!options) {
options = getCommandOptions()
options.allSuites = false
options.logLevel = options.logLevel || 'info'

if (options.fix) {
options.standard = true
Expand All @@ -31,11 +37,18 @@ const get = function () {
options.standard = true
options.allSuites = true
}
options.logLevel = LOG_LEVELS.indexOf(options.logLevel)

if (options.logLevel < 0) {
return Promise.reject(Boom.badData('Not valid log level'))
}
tracer.setLevel(options.logLevel)
states.set('options', options)
}
return Promise.resolve(options)
}

module.exports = {
get: get
get,
LOG_LEVELS
}
30 changes: 21 additions & 9 deletions lib/processes.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const Handler = function (proc, suiteData, options = {}) {
eventBus.emit('error', err)
}

const DataLogger = function (filePath, print, pendingKey) {
const DataLogger = function (filePath, pendingKey, print) {
let blankLine = ''
let writePending = 0
let closePending = false
Expand Down Expand Up @@ -140,13 +140,25 @@ const Handler = function (proc, suiteData, options = {}) {
})
}

const printLog = function (data) {
_.each(data.split('\n'), logData => {
const cleanData = _.trim(logData)
if (cleanData.length) {
logs.serviceLog({
service: suiteData.service,
log: logData
})
}
})
}

const log = function (data) {
data = _.trim(data)
if (data.length) {
let withoutColors = stripAnsi(data)
if (print) {
console.log(data)
logged.push(withoutColors)
printLog(data)
}
writePending = writePending + 1
fs.appendFile(fd, stripAnsi(`${blankLine}${withoutColors}`), 'utf8', (error) => {
Expand Down Expand Up @@ -194,9 +206,9 @@ const Handler = function (proc, suiteData, options = {}) {
options.close ? fsExtra.remove(closeFilePath) : Promise.resolve()
])
.then(() => {
const out = new DataLogger(outputFilePath, false, 'out', outputFilePath)
const err = new DataLogger(errorFilePath, false, 'err', errorFilePath)
const outerr = new DataLogger(outerrorFilePath, true, 'outerr', outerrorFilePath)
const out = new DataLogger(outputFilePath, 'out', false)
const err = new DataLogger(errorFilePath, 'err', false)
const outerr = new DataLogger(outerrorFilePath, 'outerr', true)

return Promise.props({
out: out.open(),
Expand Down Expand Up @@ -263,8 +275,8 @@ const Handler = function (proc, suiteData, options = {}) {
}

module.exports = {
fork: fork,
spawn: spawn,
execSync: execSync,
Handler: Handler
fork,
spawn,
execSync,
Handler
}
3 changes: 3 additions & 0 deletions lib/suite-docker.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const paths = require('./paths')
const states = require('./states')
const commands = require('./commands')
const processes = require('./processes')
const logs = require('./logs')

const Runner = function (config, logger) {
const name = config.name()
Expand Down Expand Up @@ -60,6 +61,7 @@ const Runner = function (config, logger) {
states.set('docker-built', true)
build = ' --build'
}
logs.dockerUp()
return runComposeSync(`up --no-start${build}`)
}

Expand Down Expand Up @@ -121,6 +123,7 @@ const Runner = function (config, logger) {
}

const runServicesAndTest = function () {
logs.dockerDown()
return runComposeSync('down').then(() => {
return runDockerUp().then(() => {
return Promise.map(config.services(), runService).then(startedServices => {
Expand Down
Loading