undo3d-run-test
Version:
A collection of utilities for testing Undo3D, and apps built with Undo3D
144 lines (107 loc) • 4.91 kB
JavaScript
//// LevelRunner (re-exported by all.mjs)
////
//// The only public class in the ‘runner’ component. An instance of LevelRunner
//// is the top level of a test, and contains several test suites.
import Hub from './hub.mjs'
import Level from './level.mjs'
import LevelSuite from './level-suite.mjs'
//// CLASS
export default class LevelRunner extends Level {
constructor (options={}) {
//// A LevelRunner always has index `1`.
if (null != options.index) throw Error('Don’t try passing in index!')
const optionsCopy = Object.assign({ index:1 }, options)
//// Create the event hub, if not in `options.hub`.
optionsCopy.hub = options.hub || new Hub()
//// Let `Level` validate, normalise and record `options`.
super(optionsCopy)
//// An array of LevelSuites.
this._testSuites = []
}
//// PUBLIC PROPERTIES
//// Xx.
get testSuites () { return this._testSuites }
//// Together, `first` and `last` make the TAP ‘plan’.
get tapPlan () {
const max = this._testSuites.length + 1 // `+ 1` because the runner is index 1
return `${Math.min(max,this.last,this.first)}..${Math.min(max,this.last)}`
}
//// The test results, as a TAP string (read-only). @TODO proper TAP string
get tap () {
//// Best practice to start a TAP with this line.
let result = [ 'TAP version 13' ]
//// A skipped runner does not show its suites.
if (this.skip) {
result = result.concat([
`${this.tapPlan} # Skipped: ${this.skip}`
, this.tapTestLine
])
//// Xx.
} else {
result = result.concat([
this.tapPlan // eg '1..300'
, this.tapTestLine // eg 'not ok 1 - All the main unit tests'
])
this.testSuites.map( suite => result = result.concat(suite.tap) )
}
return result.join('\n')
}
//// PUBLIC METHODS
//// Create a new test suite, based on `options`.
add (options) {
if (null != options.hub) throw Error('Don’t try passing in hub here!')
if (null != options.index) throw Error('Don’t try passing in index!')
const hub = this._hub
const index = this.testSuites.length + 2 // `+ 2` because the runner is index 1 @TODO nah - runner should not have an index
const optionsCopy = Object.assign({ hub, index }, options)
if (this.first > index || this.last < index) // override existing options.skip
optionsCopy.skip = `Suite ${index} is outside ${this.tapPlan}`
const testSuite = new LevelSuite(optionsCopy)
this.testSuites.push(testSuite)
return testSuite
}
//// Add an event listener. The behavior of this method depends on the `hub`
//// option passed to the constructor. If no `hub` option was passed, see
//// runner/hub.mjs for details.
on (...args) { return this._hub.on.apply(this._hub, args) }
//// Run all test suites.
async run () {
const { hub, testSuites, before, after, beforeEach, afterEach } = this
//// Potentially don’t run the runner.
if (this.skip) return //@TODO fire an event?
//// Update the suite status and fire a 'start' event.
this._status = 'running'
hub.fire('runner-start runner-update')
//// Update the runner’s status every time a suite’s status updates.
hub.on('suite-update', (eventName, suiteIndex) => {
const testSuite = testSuites[ suiteIndex - 2 ]
const oldStatus = this._status
if ( /^not ok$|^failing$/.test(testSuite.status) )
this._status = 'failing'
if (oldStatus !== this._status)
hub.fire('runner-update')
})
//// Create an array of `parcels` (empty objects), and run `before()`.
const parcels = [...Array(testSuites.length+2)].map( ()=>new Object() )
parcels[0] = parcels[1] = null // parcels at index 0 and 1 are not used
before(parcels)
//// Run suites in parallel. @TODO guard against too many threads?
async function runTestSuites () {
const promises = testSuites.map( testSuite => {
const parcel = parcels[testSuite.index] // each suite gets its own parcel
beforeEach(parcel)
const promise = testSuite.run(parcel)
afterEach(parcel)
return promise
})
await Promise.all(promises)
}
await runTestSuites.call(this)
//// Run `after()`.
after(parcels)
//// Update the status and fire an 'end' event.
this._status = 'failing' === this._status ? 'not ok' : 'ok'
hub.fire('runner-end runner-update')
////@TODO return something
}
}