Improve integration test execution time (#1005)
* Integration test: Add limit of 3 minutes per yaml file
* Monitor all test files that take more than 1m to execute
* Set the threshold to 30s
* Refactored integration test runner
* Better time reporting
* Updated test time limits
* Updated CI script
* Run oss only in oss build
* Run only oss test
* Revert "Run only oss test"
This reverts commit fd3a07d42d.
This commit is contained in:
committed by
GitHub
parent
bc970a75f3
commit
01b233428f
@ -36,7 +36,8 @@ echo -e "\033[1m>>>>> NPM run ci >>>>>>>>>>>>>>>>>>>>>>>>>>>>>\033[0m"
|
||||
|
||||
repo=$(realpath $(dirname $(realpath -s $0))/../)
|
||||
|
||||
docker run \
|
||||
if [[ $TEST_SUITE != "xpack" ]]; then
|
||||
docker run \
|
||||
--network=${NETWORK_NAME} \
|
||||
--env "TEST_ES_SERVER=${ELASTICSEARCH_URL}" \
|
||||
--env "CODECOV_TOKEN" \
|
||||
@ -46,3 +47,15 @@ docker run \
|
||||
--rm \
|
||||
elastic/elasticsearch-js \
|
||||
npm run ci
|
||||
else
|
||||
docker run \
|
||||
--network=${NETWORK_NAME} \
|
||||
--env "TEST_ES_SERVER=${ELASTICSEARCH_URL}" \
|
||||
--env "CODECOV_TOKEN" \
|
||||
--volume $repo:/usr/src/app \
|
||||
--volume /usr/src/app/node_modules \
|
||||
--name elasticsearch-js \
|
||||
--rm \
|
||||
elastic/elasticsearch-js \
|
||||
npm run test:integration
|
||||
fi
|
||||
|
||||
@ -55,4 +55,3 @@ ELASTICSEARCH_CONTAINER=${elasticsearch_image}:${ELASTICSEARCH_VERSION} \
|
||||
NODE_NAME=${NODE_NAME} \
|
||||
ELASTICSEARCH_URL=${elasticsearch_url} \
|
||||
bash .ci/run-repository.sh
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
"test": "npm run lint && npm run test:unit && npm run test:behavior && npm run test:types",
|
||||
"test:unit": "tap test/unit/*.test.js -t 300 --no-coverage",
|
||||
"test:behavior": "tap test/behavior/*.test.js -t 300 --no-coverage",
|
||||
"test:integration": "tap test/integration/index.js -T --no-coverage",
|
||||
"test:integration": "node test/integration/index.js",
|
||||
"test:types": "tsc --project ./test/types/tsconfig.json",
|
||||
"test:coverage": "nyc tap test/unit/*.test.js test/behavior/*.test.js -t 300 && nyc report --reporter=text-lcov > coverage.lcov && codecov",
|
||||
"lint": "standard",
|
||||
@ -44,6 +44,7 @@
|
||||
"dedent": "^0.7.0",
|
||||
"deepmerge": "^4.0.0",
|
||||
"dezalgo": "^1.0.3",
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"js-yaml": "^3.13.1",
|
||||
"license-checker": "^25.0.1",
|
||||
"lolex": "^4.0.1",
|
||||
|
||||
@ -8,16 +8,20 @@ const { readFileSync, accessSync, mkdirSync, readdirSync, statSync } = require('
|
||||
const { join, sep } = require('path')
|
||||
const yaml = require('js-yaml')
|
||||
const Git = require('simple-git')
|
||||
const tap = require('tap')
|
||||
const { Client } = require('../../index')
|
||||
const TestRunner = require('./test-runner')
|
||||
const build = require('./test-runner')
|
||||
const { sleep } = require('./helper')
|
||||
const ms = require('ms')
|
||||
|
||||
const esRepo = 'https://github.com/elastic/elasticsearch.git'
|
||||
const esFolder = join(__dirname, '..', '..', 'elasticsearch')
|
||||
const yamlFolder = join(esFolder, 'rest-api-spec', 'src', 'main', 'resources', 'rest-api-spec', 'test')
|
||||
const xPackYamlFolder = join(esFolder, 'x-pack', 'plugin', 'src', 'test', 'resources', 'rest-api-spec', 'test')
|
||||
|
||||
const MAX_API_TIME = 1000 * 90
|
||||
const MAX_FILE_TIME = 1000 * 30
|
||||
const MAX_TEST_TIME = 1000 * 2
|
||||
|
||||
const ossSkips = {
|
||||
// TODO: remove this once 'arbitrary_key' is implemented
|
||||
// https://github.com/elastic/elasticsearch/pull/41492
|
||||
@ -66,8 +70,7 @@ const xPackBlackList = {
|
||||
'xpack/15_basic.yml': ['*']
|
||||
}
|
||||
|
||||
class Runner {
|
||||
constructor (opts = {}) {
|
||||
function runner (opts = {}) {
|
||||
const options = { node: opts.node }
|
||||
if (opts.isXPack) {
|
||||
options.ssl = {
|
||||
@ -75,41 +78,38 @@ class Runner {
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
}
|
||||
this.client = new Client(options)
|
||||
console.log('Loading yaml suite')
|
||||
}
|
||||
const client = new Client(options)
|
||||
log('Loading yaml suite')
|
||||
start({ client, isXPack: opts.isXPack })
|
||||
.catch(console.log)
|
||||
}
|
||||
|
||||
async waitCluster (client, times = 0) {
|
||||
async function waitCluster (client, times = 0) {
|
||||
try {
|
||||
await client.cluster.health({ waitForStatus: 'green', timeout: '50s' })
|
||||
} catch (err) {
|
||||
if (++times < 10) {
|
||||
await sleep(5000)
|
||||
return this.waitCluster(client, times)
|
||||
return waitCluster(client, times)
|
||||
}
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async start ({ isXPack }) {
|
||||
const { client } = this
|
||||
const parse = this.parse.bind(this)
|
||||
|
||||
console.log('Waiting for Elasticsearch')
|
||||
await this.waitCluster(client)
|
||||
async function start ({ client, isXPack }) {
|
||||
log('Waiting for Elasticsearch')
|
||||
await waitCluster(client)
|
||||
|
||||
const { body } = await client.info()
|
||||
const { number: version, build_hash: sha } = body.version
|
||||
|
||||
console.log(`Checking out sha ${sha}...`)
|
||||
await this.withSHA(sha)
|
||||
log(`Checking out sha ${sha}...`)
|
||||
await withSHA(sha)
|
||||
|
||||
console.log(`Testing ${isXPack ? 'XPack' : 'oss'} api...`)
|
||||
log(`Testing ${isXPack ? 'XPack' : 'oss'} api...`)
|
||||
|
||||
const folders = []
|
||||
.concat(getAllFiles(yamlFolder))
|
||||
.concat(isXPack ? getAllFiles(xPackYamlFolder) : [])
|
||||
const folders = getAllFiles(isXPack ? xPackYamlFolder : yamlFolder)
|
||||
.filter(t => !/(README|TODO)/g.test(t))
|
||||
// we cluster the array based on the folder names,
|
||||
// to provide a better test log output
|
||||
@ -127,6 +127,7 @@ class Runner {
|
||||
return arr
|
||||
}, [])
|
||||
|
||||
const totalTime = now()
|
||||
for (const folder of folders) {
|
||||
// pretty name
|
||||
const apiName = folder[0].slice(
|
||||
@ -134,8 +135,16 @@ class Runner {
|
||||
folder[0].lastIndexOf(sep)
|
||||
)
|
||||
|
||||
tap.test(`Testing ${apiName}`, { bail: true, timeout: 0 }, t => {
|
||||
log('Testing ' + apiName.slice(1))
|
||||
const apiTime = now()
|
||||
|
||||
for (const file of folder) {
|
||||
const testRunner = build({
|
||||
client,
|
||||
version,
|
||||
isXPack: file.includes('x-pack')
|
||||
})
|
||||
const fileTime = now()
|
||||
const data = readFileSync(file, 'utf8')
|
||||
// get the test yaml (as object), some file has multiple yaml documents inside,
|
||||
// every document is separated by '---', so we split on the separator
|
||||
@ -146,17 +155,6 @@ class Runner {
|
||||
.filter(Boolean)
|
||||
.map(parse)
|
||||
|
||||
t.test(
|
||||
file.slice(file.lastIndexOf(apiName)),
|
||||
testFile(file, tests)
|
||||
)
|
||||
}
|
||||
t.end()
|
||||
})
|
||||
}
|
||||
|
||||
function testFile (file, tests) {
|
||||
return t => {
|
||||
// get setup and teardown if present
|
||||
var setupTest = null
|
||||
var teardownTest = null
|
||||
@ -165,28 +163,55 @@ class Runner {
|
||||
if (test.teardown) teardownTest = test.teardown
|
||||
}
|
||||
|
||||
tests.forEach(test => {
|
||||
const cleanPath = file.slice(file.lastIndexOf(apiName))
|
||||
log(' ' + cleanPath)
|
||||
|
||||
for (const test of tests) {
|
||||
const testTime = now()
|
||||
const name = Object.keys(test)[0]
|
||||
if (name === 'setup' || name === 'teardown') return
|
||||
if (shouldSkip(t, isXPack, file, name)) return
|
||||
|
||||
// create a subtest for the specific folder + test file + test name
|
||||
t.test(name, async t => {
|
||||
const testRunner = new TestRunner({
|
||||
client,
|
||||
version,
|
||||
tap: t,
|
||||
isXPack: file.includes('x-pack')
|
||||
})
|
||||
if (name === 'setup' || name === 'teardown') continue
|
||||
if (shouldSkip(isXPack, file, name)) continue
|
||||
log(' - ' + name)
|
||||
try {
|
||||
await testRunner.run(setupTest, test[name], teardownTest)
|
||||
})
|
||||
})
|
||||
t.end()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
const totalTestTime = now() - testTime
|
||||
if (totalTestTime > MAX_TEST_TIME) {
|
||||
log(' took too long: ' + ms(totalTestTime))
|
||||
} else {
|
||||
log(' took: ' + ms(totalTestTime))
|
||||
}
|
||||
}
|
||||
const totalFileTime = now() - fileTime
|
||||
if (totalFileTime > MAX_FILE_TIME) {
|
||||
log(` ${cleanPath} took too long: ` + ms(totalFileTime))
|
||||
} else {
|
||||
log(` ${cleanPath} took: ` + ms(totalFileTime))
|
||||
}
|
||||
}
|
||||
const totalApiTime = now() - apiTime
|
||||
if (totalApiTime > MAX_API_TIME) {
|
||||
log(`${apiName} took too long: ` + ms(totalApiTime))
|
||||
} else {
|
||||
log(`${apiName} took: ` + ms(totalApiTime))
|
||||
}
|
||||
}
|
||||
log(`Total testing time: ${ms(now() - totalTime)}`)
|
||||
}
|
||||
|
||||
parse (data) {
|
||||
function log (text) {
|
||||
process.stdout.write(text + '\n')
|
||||
}
|
||||
|
||||
function now () {
|
||||
var ts = process.hrtime()
|
||||
return (ts[0] * 1e3) + (ts[1] / 1e6)
|
||||
}
|
||||
|
||||
function parse (data) {
|
||||
try {
|
||||
var doc = yaml.safeLoad(data)
|
||||
} catch (err) {
|
||||
@ -194,14 +219,9 @@ class Runner {
|
||||
return
|
||||
}
|
||||
return doc
|
||||
}
|
||||
}
|
||||
|
||||
getTest (folder) {
|
||||
const tests = readdirSync(folder)
|
||||
return tests.filter(t => !/(README|TODO)/g.test(t))
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Sets the elasticsearch repository to the given sha.
|
||||
* If the repository is not present in `esFolder` it will
|
||||
* clone the repository and the checkout the sha.
|
||||
@ -210,17 +230,17 @@ class Runner {
|
||||
* @param {string} sha
|
||||
* @param {function} callback
|
||||
*/
|
||||
withSHA (sha) {
|
||||
function withSHA (sha) {
|
||||
return new Promise((resolve, reject) => {
|
||||
_withSHA.call(this, err => err ? reject(err) : resolve())
|
||||
_withSHA(err => err ? reject(err) : resolve())
|
||||
})
|
||||
|
||||
function _withSHA (callback) {
|
||||
var fresh = false
|
||||
var retry = 0
|
||||
|
||||
if (!this.pathExist(esFolder)) {
|
||||
if (!this.createFolder(esFolder)) {
|
||||
if (!pathExist(esFolder)) {
|
||||
if (!createFolder(esFolder)) {
|
||||
return callback(new Error('Failed folder creation'))
|
||||
}
|
||||
fresh = true
|
||||
@ -235,7 +255,7 @@ class Runner {
|
||||
}
|
||||
|
||||
function checkout () {
|
||||
console.log(`Checking out sha '${sha}'`)
|
||||
log(`Checking out sha '${sha}'`)
|
||||
git.checkout(sha, err => {
|
||||
if (err) {
|
||||
if (retry++ > 0) {
|
||||
@ -248,7 +268,7 @@ class Runner {
|
||||
}
|
||||
|
||||
function pull (cb) {
|
||||
console.log('Pulling elasticsearch repository...')
|
||||
log('Pulling elasticsearch repository...')
|
||||
git.pull(err => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
@ -258,7 +278,7 @@ class Runner {
|
||||
}
|
||||
|
||||
function clone (cb) {
|
||||
console.log('Cloning elasticsearch repository...')
|
||||
log('Cloning elasticsearch repository...')
|
||||
git.clone(esRepo, esFolder, err => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
@ -267,35 +287,34 @@ class Runner {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Checks if the given path exists
|
||||
* @param {string} path
|
||||
* @returns {boolean} true if exists, false if not
|
||||
*/
|
||||
pathExist (path) {
|
||||
function pathExist (path) {
|
||||
try {
|
||||
accessSync(path)
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Creates the given folder
|
||||
* @param {string} name
|
||||
* @returns {boolean} true on success, false on failure
|
||||
*/
|
||||
createFolder (name) {
|
||||
function createFolder (name) {
|
||||
try {
|
||||
mkdirSync(name)
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
@ -304,18 +323,17 @@ if (require.main === module) {
|
||||
node,
|
||||
isXPack: node.indexOf('@') > -1
|
||||
}
|
||||
const runner = new Runner(opts)
|
||||
runner.start(opts).catch(console.log)
|
||||
runner(opts)
|
||||
}
|
||||
|
||||
const shouldSkip = (t, isXPack, file, name) => {
|
||||
const shouldSkip = (isXPack, file, name) => {
|
||||
var list = Object.keys(ossSkips)
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
const ossTest = ossSkips[list[i]]
|
||||
for (var j = 0; j < ossTest.length; j++) {
|
||||
if (file.endsWith(list[i]) && (name === ossTest[j] || ossTest[j] === '*')) {
|
||||
const testName = file.slice(file.indexOf(`${sep}elasticsearch${sep}`)) + ' / ' + name
|
||||
t.comment(`Skipping test ${testName} because is blacklisted in the oss test`)
|
||||
log(`Skipping test ${testName} because is blacklisted in the oss test`)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -328,7 +346,7 @@ const shouldSkip = (t, isXPack, file, name) => {
|
||||
for (j = 0; j < platTest.length; j++) {
|
||||
if (file.endsWith(list[i]) && (name === platTest[j] || platTest[j] === '*')) {
|
||||
const testName = file.slice(file.indexOf(`${sep}elasticsearch${sep}`)) + ' / ' + name
|
||||
t.comment(`Skipping test ${testName} because is blacklisted in the XPack test`)
|
||||
log(`Skipping test ${testName} because is blacklisted in the XPack test`)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -345,4 +363,4 @@ const getAllFiles = dir =>
|
||||
return isDirectory ? [...files, ...getAllFiles(name)] : [...files, name]
|
||||
}, [])
|
||||
|
||||
module.exports = Runner
|
||||
module.exports = runner
|
||||
|
||||
@ -6,9 +6,10 @@
|
||||
|
||||
/* eslint camelcase: 0 */
|
||||
|
||||
const t = require('tap')
|
||||
const assert = require('assert')
|
||||
const semver = require('semver')
|
||||
const helper = require('./helper')
|
||||
const deepEqual = require('fast-deep-equal')
|
||||
const { ConfigurationError } = require('../../lib/errors')
|
||||
|
||||
const { delve, to } = helper
|
||||
@ -25,63 +26,58 @@ const supportedFeatures = [
|
||||
'arbitrary_key'
|
||||
]
|
||||
|
||||
class TestRunner {
|
||||
constructor (opts = {}) {
|
||||
opts = opts || {}
|
||||
|
||||
this.client = opts.client
|
||||
this.esVersion = opts.version
|
||||
this.response = null
|
||||
this.stash = new Map()
|
||||
this.tap = opts.tap || t
|
||||
this.isXPack = opts.isXPack
|
||||
}
|
||||
function build (opts = {}) {
|
||||
const client = opts.client
|
||||
const esVersion = opts.version
|
||||
const isXPack = opts.isXPack
|
||||
const stash = new Map()
|
||||
let response = null
|
||||
|
||||
/**
|
||||
* Runs a cleanup, removes all indices, aliases, templates, and snapshots
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async cleanup () {
|
||||
this.tap.comment('Cleanup')
|
||||
async function cleanup () {
|
||||
// // tap.comment('Cleanup')
|
||||
|
||||
this.response = null
|
||||
this.stash = new Map()
|
||||
response = null
|
||||
stash.clear()
|
||||
|
||||
try {
|
||||
await this.client.indices.delete({ index: '_all' }, { ignore: 404 })
|
||||
await client.indices.delete({ index: '_all' }, { ignore: 404 })
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: indices.delete')
|
||||
assert.ifError(err, 'should not error: indices.delete')
|
||||
}
|
||||
|
||||
try {
|
||||
await this.client.indices.deleteAlias({ index: '_all', name: '_all' }, { ignore: 404 })
|
||||
await client.indices.deleteAlias({ index: '_all', name: '_all' }, { ignore: 404 })
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: indices.deleteAlias')
|
||||
assert.ifError(err, 'should not error: indices.deleteAlias')
|
||||
}
|
||||
|
||||
try {
|
||||
const { body: templates } = await this.client.indices.getTemplate()
|
||||
const { body: templates } = await client.indices.getTemplate()
|
||||
await helper.runInParallel(
|
||||
this.client, 'indices.deleteTemplate',
|
||||
client, 'indices.deleteTemplate',
|
||||
Object.keys(templates).map(t => ({ name: t }))
|
||||
)
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: indices.deleteTemplate')
|
||||
assert.ifError(err, 'should not error: indices.deleteTemplate')
|
||||
}
|
||||
|
||||
try {
|
||||
const { body: repositories } = await this.client.snapshot.getRepository()
|
||||
const { body: repositories } = await client.snapshot.getRepository()
|
||||
for (const repository of Object.keys(repositories)) {
|
||||
const { body: snapshots } = await this.client.snapshot.get({ repository, snapshot: '_all' })
|
||||
const { body: snapshots } = await client.snapshot.get({ repository, snapshot: '_all' })
|
||||
await helper.runInParallel(
|
||||
this.client, 'snapshot.delete',
|
||||
client, 'snapshot.delete',
|
||||
Object.keys(snapshots).map(snapshot => ({ snapshot, repository })),
|
||||
{ ignore: [404] }
|
||||
)
|
||||
await this.client.snapshot.deleteRepository({ repository }, { ignore: [404] })
|
||||
await client.snapshot.deleteRepository({ repository }, { ignore: [404] })
|
||||
}
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: snapshot.delete / snapshot.deleteRepository')
|
||||
assert.ifError(err, 'should not error: snapshot.delete / snapshot.deleteRepository')
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,33 +86,33 @@ class TestRunner {
|
||||
* This set of calls should be executed before the final clenup.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async cleanupXPack () {
|
||||
this.tap.comment('XPack Cleanup')
|
||||
async function cleanupXPack () {
|
||||
// tap.comment('XPack Cleanup')
|
||||
|
||||
try {
|
||||
const { body } = await this.client.security.getRole()
|
||||
const { body } = await client.security.getRole()
|
||||
const roles = Object.keys(body).filter(n => helper.esDefaultRoles.indexOf(n) === -1)
|
||||
await helper.runInParallel(
|
||||
this.client, 'security.deleteRole',
|
||||
client, 'security.deleteRole',
|
||||
roles.map(r => ({ name: r }))
|
||||
)
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: security role cleanup')
|
||||
assert.ifError(err, 'should not error: security role cleanup')
|
||||
}
|
||||
|
||||
try {
|
||||
const { body } = await this.client.security.getUser()
|
||||
const { body } = await client.security.getUser()
|
||||
const users = Object.keys(body).filter(n => helper.esDefaultUsers.indexOf(n) === -1)
|
||||
await helper.runInParallel(
|
||||
this.client, 'security.deleteUser',
|
||||
client, 'security.deleteUser',
|
||||
users.map(r => ({ username: r }))
|
||||
)
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: security user cleanup')
|
||||
assert.ifError(err, 'should not error: security user cleanup')
|
||||
}
|
||||
|
||||
try {
|
||||
const { body } = await this.client.security.getPrivileges()
|
||||
const { body } = await client.security.getPrivileges()
|
||||
const privileges = []
|
||||
Object.keys(body).forEach(app => {
|
||||
Object.keys(body[app]).forEach(priv => {
|
||||
@ -126,52 +122,52 @@ class TestRunner {
|
||||
})
|
||||
})
|
||||
})
|
||||
await helper.runInParallel(this.client, 'security.deletePrivileges', privileges)
|
||||
await helper.runInParallel(client, 'security.deletePrivileges', privileges)
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: security privileges cleanup')
|
||||
assert.ifError(err, 'should not error: security privileges cleanup')
|
||||
}
|
||||
|
||||
try {
|
||||
await this.client.ml.stopDatafeed({ datafeedId: '*', force: true })
|
||||
const { body } = await this.client.ml.getDatafeeds({ datafeedId: '*' })
|
||||
await client.ml.stopDatafeed({ datafeedId: '*', force: true })
|
||||
const { body } = await client.ml.getDatafeeds({ datafeedId: '*' })
|
||||
const feeds = body.datafeeds.map(f => f.datafeed_id)
|
||||
await helper.runInParallel(
|
||||
this.client, 'ml.deleteDatafeed',
|
||||
client, 'ml.deleteDatafeed',
|
||||
feeds.map(f => ({ datafeedId: f }))
|
||||
)
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should error: not ml datafeed cleanup')
|
||||
assert.ifError(err, 'should error: not ml datafeed cleanup')
|
||||
}
|
||||
|
||||
try {
|
||||
await this.client.ml.closeJob({ jobId: '*', force: true })
|
||||
const { body } = await this.client.ml.getJobs({ jobId: '*' })
|
||||
await client.ml.closeJob({ jobId: '*', force: true })
|
||||
const { body } = await client.ml.getJobs({ jobId: '*' })
|
||||
const jobs = body.jobs.map(j => j.job_id)
|
||||
await helper.runInParallel(
|
||||
this.client, 'ml.deleteJob',
|
||||
client, 'ml.deleteJob',
|
||||
jobs.map(j => ({ jobId: j, waitForCompletion: true, force: true }))
|
||||
)
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: ml job cleanup')
|
||||
assert.ifError(err, 'should not error: ml job cleanup')
|
||||
}
|
||||
|
||||
try {
|
||||
const { body } = await this.client.rollup.getJobs({ id: '_all' })
|
||||
const { body } = await client.rollup.getJobs({ id: '_all' })
|
||||
const jobs = body.jobs.map(j => j.config.id)
|
||||
await helper.runInParallel(
|
||||
this.client, 'rollup.stopJob',
|
||||
client, 'rollup.stopJob',
|
||||
jobs.map(j => ({ id: j, waitForCompletion: true }))
|
||||
)
|
||||
await helper.runInParallel(
|
||||
this.client, 'rollup.deleteJob',
|
||||
client, 'rollup.deleteJob',
|
||||
jobs.map(j => ({ id: j }))
|
||||
)
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: rollup jobs cleanup')
|
||||
assert.ifError(err, 'should not error: rollup jobs cleanup')
|
||||
}
|
||||
|
||||
try {
|
||||
const { body } = await this.client.tasks.list()
|
||||
const { body } = await client.tasks.list()
|
||||
const tasks = Object.keys(body.nodes)
|
||||
.reduce((acc, node) => {
|
||||
const { tasks } = body.nodes[node]
|
||||
@ -182,24 +178,24 @@ class TestRunner {
|
||||
}, [])
|
||||
|
||||
await helper.runInParallel(
|
||||
this.client, 'tasks.cancel',
|
||||
client, 'tasks.cancel',
|
||||
tasks.map(id => ({ taskId: id }))
|
||||
)
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: tasks cleanup')
|
||||
assert.ifError(err, 'should not error: tasks cleanup')
|
||||
}
|
||||
|
||||
try {
|
||||
await this.client.ilm.removePolicy({ index: '_all' })
|
||||
await client.ilm.removePolicy({ index: '_all' })
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: ilm.removePolicy')
|
||||
assert.ifError(err, 'should not error: ilm.removePolicy')
|
||||
}
|
||||
|
||||
// refresh the all indexes
|
||||
try {
|
||||
await this.client.indices.refresh({ index: '_all' })
|
||||
await client.indices.refresh({ index: '_all' })
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: indices.refresh')
|
||||
assert.ifError(err, 'should not error: indices.refresh')
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,117 +214,37 @@ class TestRunner {
|
||||
* @oaram {object} teardown (null if not needed)
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async run (setup, test, teardown) {
|
||||
async function run (setup, test, teardown) {
|
||||
// if we should skip a feature in the setup/teardown section
|
||||
// we should skip the entire test file
|
||||
const skip = getSkip(setup) || getSkip(teardown)
|
||||
if (skip && this.shouldSkip(skip)) {
|
||||
this.skip(skip)
|
||||
if (skip && shouldSkip(esVersion, skip)) {
|
||||
skip(skip)
|
||||
return
|
||||
}
|
||||
|
||||
if (this.isXPack) {
|
||||
if (isXPack) {
|
||||
// Some xpack test requires this user
|
||||
this.tap.comment('Creating x-pack user')
|
||||
// tap.comment('Creating x-pack user')
|
||||
try {
|
||||
await this.client.security.putUser({
|
||||
await client.security.putUser({
|
||||
username: 'x_pack_rest_user',
|
||||
body: { password: 'x-pack-test-password', roles: ['superuser'] }
|
||||
})
|
||||
} catch (err) {
|
||||
this.tap.error(err, 'should not error: security.putUser')
|
||||
assert.ifError(err, 'should not error: security.putUser')
|
||||
}
|
||||
}
|
||||
|
||||
if (setup) await this.exec('Setup', setup)
|
||||
if (setup) await exec('Setup', setup)
|
||||
|
||||
await this.exec('Test', test)
|
||||
await exec('Test', test)
|
||||
|
||||
if (teardown) await this.exec('Teardown', teardown)
|
||||
if (teardown) await exec('Teardown', teardown)
|
||||
|
||||
if (this.isXPack) await this.cleanupXPack()
|
||||
if (isXPack) await cleanupXPack()
|
||||
|
||||
await this.cleanup()
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a skip
|
||||
* @param {object} the actions
|
||||
* @returns {TestRunner}
|
||||
*/
|
||||
skip (action) {
|
||||
if (action.reason && action.version) {
|
||||
this.tap.comment(`Skip: ${action.reason} (${action.version})`)
|
||||
} else if (action.features) {
|
||||
this.tap.comment(`Skip: ${JSON.stringify(action.features)})`)
|
||||
} else {
|
||||
this.tap.comment('Skipped')
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides if a test should be skipped
|
||||
* @param {object} the actions
|
||||
* @returns {boolean}
|
||||
*/
|
||||
shouldSkip (action) {
|
||||
var shouldSkip = false
|
||||
// skip based on the version
|
||||
if (action.version) {
|
||||
if (action.version.trim() === 'all') return true
|
||||
const [min, max] = action.version.split('-').map(v => v.trim())
|
||||
// if both `min` and `max` are specified
|
||||
if (min && max) {
|
||||
shouldSkip = semver.satisfies(this.esVersion, action.version)
|
||||
// if only `min` is specified
|
||||
} else if (min) {
|
||||
shouldSkip = semver.gte(this.esVersion, min)
|
||||
// if only `max` is specified
|
||||
} else if (max) {
|
||||
shouldSkip = semver.lte(this.esVersion, max)
|
||||
// something went wrong!
|
||||
} else {
|
||||
throw new Error(`skip: Bad version range: ${action.version}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSkip) return true
|
||||
|
||||
if (action.features) {
|
||||
if (!Array.isArray(action.features)) action.features = [action.features]
|
||||
// returns true if one of the features is not present in the supportedFeatures
|
||||
shouldSkip = !!action.features.filter(f => !~supportedFeatures.indexOf(f)).length
|
||||
}
|
||||
|
||||
if (shouldSkip) return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the array syntax of keys and values
|
||||
* eg: 'hits.hits.1.stuff' to 'hits.hits[1].stuff'
|
||||
* @param {object} the action to update
|
||||
* @returns {obj} the updated action
|
||||
*/
|
||||
updateArraySyntax (obj) {
|
||||
const newObj = {}
|
||||
|
||||
for (const key in obj) {
|
||||
const newKey = key.replace(/\.\d{1,}\./g, v => `[${v.slice(1, -1)}].`)
|
||||
const val = obj[key]
|
||||
|
||||
if (typeof val === 'string') {
|
||||
newObj[newKey] = val.replace(/\.\d{1,}\./g, v => `[${v.slice(1, -1)}].`)
|
||||
} else if (val !== null && typeof val === 'object') {
|
||||
newObj[newKey] = this.updateArraySyntax(val)
|
||||
} else {
|
||||
newObj[newKey] = val
|
||||
}
|
||||
}
|
||||
|
||||
return newObj
|
||||
await cleanup()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -340,9 +256,9 @@ class TestRunner {
|
||||
* @param {object|string} the action to update
|
||||
* @returns {object|string} the updated action
|
||||
*/
|
||||
fillStashedValues (obj) {
|
||||
function fillStashedValues (obj) {
|
||||
if (typeof obj === 'string') {
|
||||
return getStashedValues.call(this, obj)
|
||||
return getStashedValues(obj)
|
||||
}
|
||||
// iterate every key of the object
|
||||
for (const key in obj) {
|
||||
@ -355,7 +271,7 @@ class TestRunner {
|
||||
const start = val.indexOf('${')
|
||||
const end = val.indexOf('}', val.indexOf('${'))
|
||||
const stashedKey = val.slice(start + 2, end)
|
||||
const stashed = this.stash.get(stashedKey)
|
||||
const stashed = stash.get(stashedKey)
|
||||
obj[key] = val.slice(0, start) + stashed + val.slice(end + 1)
|
||||
continue
|
||||
}
|
||||
@ -364,7 +280,7 @@ class TestRunner {
|
||||
const start = val.indexOf('"$')
|
||||
const end = val.indexOf('"', start + 1)
|
||||
const stashedKey = val.slice(start + 2, end)
|
||||
const stashed = '"' + this.stash.get(stashedKey) + '"'
|
||||
const stashed = '"' + stash.get(stashedKey) + '"'
|
||||
obj[key] = val.slice(0, start) + stashed + val.slice(end + 1)
|
||||
continue
|
||||
}
|
||||
@ -372,13 +288,13 @@ class TestRunner {
|
||||
// we run the "update value" code
|
||||
if (typeof val === 'string' && val.includes('$')) {
|
||||
// update the key value
|
||||
obj[key] = getStashedValues.call(this, val)
|
||||
obj[key] = getStashedValues(val)
|
||||
continue
|
||||
}
|
||||
|
||||
// go deep in the object
|
||||
if (val !== null && typeof val === 'object') {
|
||||
this.fillStashedValues(val)
|
||||
fillStashedValues(val)
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,7 +308,7 @@ class TestRunner {
|
||||
// we update every field that start with '$'
|
||||
.map(part => {
|
||||
if (part[0] === '$') {
|
||||
const stashed = this.stash.get(part.slice(1))
|
||||
const stashed = stash.get(part.slice(1))
|
||||
if (stashed == null) {
|
||||
throw new Error(`Cannot find stashed value '${part}' for '${JSON.stringify(obj)}'`)
|
||||
}
|
||||
@ -414,22 +330,21 @@ class TestRunner {
|
||||
* @param {string} the name to identify the stashed value
|
||||
* @returns {TestRunner}
|
||||
*/
|
||||
set (key, name) {
|
||||
function set (key, name) {
|
||||
if (key.includes('_arbitrary_key_')) {
|
||||
var currentVisit = null
|
||||
for (const path of key.split('.')) {
|
||||
if (path === '_arbitrary_key_') {
|
||||
const keys = Object.keys(currentVisit)
|
||||
const arbitraryKey = keys[getRandomInt(0, keys.length)]
|
||||
this.stash.set(name, arbitraryKey)
|
||||
stash.set(name, arbitraryKey)
|
||||
} else {
|
||||
currentVisit = delve(this.response, path)
|
||||
currentVisit = delve(response, path)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.stash.set(name, delve(this.response, key))
|
||||
stash.set(name, delve(response, key))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
@ -438,18 +353,17 @@ class TestRunner {
|
||||
* @param {string} the transformation function as string
|
||||
* @returns {TestRunner}
|
||||
*/
|
||||
transform_and_set (name, transform) {
|
||||
function transform_and_set (name, transform) {
|
||||
if (/base64EncodeCredentials/.test(transform)) {
|
||||
const [user, password] = transform
|
||||
.slice(transform.indexOf('(') + 1, -1)
|
||||
.replace(/ /g, '')
|
||||
.split(',')
|
||||
const userAndPassword = `${delve(this.response, user)}:${delve(this.response, password)}`
|
||||
this.stash.set(name, Buffer.from(userAndPassword).toString('base64'))
|
||||
const userAndPassword = `${delve(response, user)}:${delve(response, password)}`
|
||||
stash.set(name, Buffer.from(userAndPassword).toString('base64'))
|
||||
} else {
|
||||
throw new Error(`Unknown transform: '${transform}'`)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
@ -457,9 +371,9 @@ class TestRunner {
|
||||
* @param {object} the action to perform
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async do (action) {
|
||||
const cmd = this.parseDo(action)
|
||||
const api = delve(this.client, cmd.method).bind(this.client)
|
||||
async function doAction (action) {
|
||||
const cmd = parseDo(action)
|
||||
const api = delve(client, cmd.method).bind(client)
|
||||
|
||||
const options = { ignore: cmd.params.ignore, headers: action.headers }
|
||||
if (cmd.params.ignore) delete cmd.params.ignore
|
||||
@ -469,7 +383,7 @@ class TestRunner {
|
||||
var body = result ? result.body : null
|
||||
|
||||
if (action.warnings && warnings === null) {
|
||||
this.tap.fail('We should get a warning header', action.warnings)
|
||||
assert.fail('We should get a warning header', action.warnings)
|
||||
} else if (!action.warnings && warnings !== null) {
|
||||
// if there is only the 'default shard will change'
|
||||
// warning we skip the check, because the yaml
|
||||
@ -482,7 +396,7 @@ class TestRunner {
|
||||
})
|
||||
|
||||
if (hasDefaultShardsWarning === true && warnings.length > 1) {
|
||||
this.tap.fail('We are not expecting warnings', warnings)
|
||||
assert.fail('We are not expecting warnings', warnings)
|
||||
}
|
||||
} else if (action.warnings && warnings !== null) {
|
||||
// if the yaml warnings do not contain the
|
||||
@ -500,22 +414,22 @@ class TestRunner {
|
||||
warnings = warnings.filter(h => !h.test(/default\snumber\sof\sshards/g))
|
||||
}
|
||||
|
||||
this.tap.deepEqual(warnings, action.warnings)
|
||||
assert.ok(deepEqual(warnings, action.warnings))
|
||||
}
|
||||
|
||||
if (action.catch) {
|
||||
this.tap.true(
|
||||
assert.ok(
|
||||
parseDoError(err, action.catch),
|
||||
`the error should be: ${action.catch}`
|
||||
)
|
||||
try {
|
||||
this.response = JSON.parse(err.body)
|
||||
response = JSON.parse(err.body)
|
||||
} catch (e) {
|
||||
this.response = err.body
|
||||
response = err.body
|
||||
}
|
||||
} else {
|
||||
this.tap.error(err, `should not error: ${cmd.method}`, action)
|
||||
this.response = body
|
||||
assert.ifError(err, `should not error: ${cmd.method}`, action)
|
||||
response = body
|
||||
}
|
||||
}
|
||||
|
||||
@ -525,138 +439,139 @@ class TestRunner {
|
||||
* @param {object} the actions to perform
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async exec (name, actions) {
|
||||
this.tap.comment(name)
|
||||
async function exec (name, actions) {
|
||||
// tap.comment(name)
|
||||
for (const action of actions) {
|
||||
if (action.skip) {
|
||||
if (this.shouldSkip(action.skip)) {
|
||||
this.skip(this.fillStashedValues(action.skip))
|
||||
if (shouldSkip(esVersion, action.skip)) {
|
||||
skip(fillStashedValues(action.skip))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (action.do) {
|
||||
await this.do(this.fillStashedValues(action.do))
|
||||
await doAction(fillStashedValues(action.do))
|
||||
}
|
||||
|
||||
if (action.set) {
|
||||
const key = Object.keys(action.set)[0]
|
||||
this.set(this.fillStashedValues(key), action.set[key])
|
||||
set(fillStashedValues(key), action.set[key])
|
||||
}
|
||||
|
||||
if (action.transform_and_set) {
|
||||
const key = Object.keys(action.transform_and_set)[0]
|
||||
this.transform_and_set(key, action.transform_and_set[key])
|
||||
transform_and_set(key, action.transform_and_set[key])
|
||||
}
|
||||
|
||||
if (action.match) {
|
||||
const key = Object.keys(action.match)[0]
|
||||
this.match(
|
||||
match(
|
||||
// in some cases, the yaml refers to the body with an empty string
|
||||
key === '$body' || key === ''
|
||||
? this.response
|
||||
: delve(this.response, this.fillStashedValues(key)),
|
||||
? response
|
||||
: delve(response, fillStashedValues(key)),
|
||||
key === '$body'
|
||||
? action.match[key]
|
||||
: this.fillStashedValues(action.match)[key],
|
||||
: fillStashedValues(action.match)[key],
|
||||
action.match
|
||||
)
|
||||
}
|
||||
|
||||
if (action.lt) {
|
||||
const key = Object.keys(action.lt)[0]
|
||||
this.lt(
|
||||
delve(this.response, this.fillStashedValues(key)),
|
||||
this.fillStashedValues(action.lt)[key]
|
||||
lt(
|
||||
delve(response, fillStashedValues(key)),
|
||||
fillStashedValues(action.lt)[key]
|
||||
)
|
||||
}
|
||||
|
||||
if (action.gt) {
|
||||
const key = Object.keys(action.gt)[0]
|
||||
this.gt(
|
||||
delve(this.response, this.fillStashedValues(key)),
|
||||
this.fillStashedValues(action.gt)[key]
|
||||
gt(
|
||||
delve(response, fillStashedValues(key)),
|
||||
fillStashedValues(action.gt)[key]
|
||||
)
|
||||
}
|
||||
|
||||
if (action.lte) {
|
||||
const key = Object.keys(action.lte)[0]
|
||||
this.lte(
|
||||
delve(this.response, this.fillStashedValues(key)),
|
||||
this.fillStashedValues(action.lte)[key]
|
||||
lte(
|
||||
delve(response, fillStashedValues(key)),
|
||||
fillStashedValues(action.lte)[key]
|
||||
)
|
||||
}
|
||||
|
||||
if (action.gte) {
|
||||
const key = Object.keys(action.gte)[0]
|
||||
this.gte(
|
||||
delve(this.response, this.fillStashedValues(key)),
|
||||
this.fillStashedValues(action.gte)[key]
|
||||
gte(
|
||||
delve(response, fillStashedValues(key)),
|
||||
fillStashedValues(action.gte)[key]
|
||||
)
|
||||
}
|
||||
|
||||
if (action.length) {
|
||||
const key = Object.keys(action.length)[0]
|
||||
this.length(
|
||||
length(
|
||||
key === '$body' || key === ''
|
||||
? this.response
|
||||
: delve(this.response, this.fillStashedValues(key)),
|
||||
? response
|
||||
: delve(response, fillStashedValues(key)),
|
||||
key === '$body'
|
||||
? action.length[key]
|
||||
: this.fillStashedValues(action.length)[key]
|
||||
: fillStashedValues(action.length)[key]
|
||||
)
|
||||
}
|
||||
|
||||
if (action.is_true) {
|
||||
const isTrue = this.fillStashedValues(action.is_true)
|
||||
this.is_true(
|
||||
delve(this.response, isTrue),
|
||||
const isTrue = fillStashedValues(action.is_true)
|
||||
is_true(
|
||||
delve(response, isTrue),
|
||||
isTrue
|
||||
)
|
||||
}
|
||||
|
||||
if (action.is_false) {
|
||||
const isFalse = this.fillStashedValues(action.is_false)
|
||||
this.is_false(
|
||||
delve(this.response, isFalse),
|
||||
const isFalse = fillStashedValues(action.is_false)
|
||||
is_false(
|
||||
delve(response, isFalse),
|
||||
isFalse
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
return { run }
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given value is truthy
|
||||
* @param {any} the value to check
|
||||
* @param {string} an optional message
|
||||
* @returns {TestRunner}
|
||||
*/
|
||||
is_true (val, msg) {
|
||||
this.tap.true(val, `expect truthy value: ${msg} - value: ${JSON.stringify(val)}`)
|
||||
return this
|
||||
}
|
||||
function is_true (val, msg) {
|
||||
assert.ok(val, `expect truthy value: ${msg} - value: ${JSON.stringify(val)}`)
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Asserts that the given value is falsey
|
||||
* @param {any} the value to check
|
||||
* @param {string} an optional message
|
||||
* @returns {TestRunner}
|
||||
*/
|
||||
is_false (val, msg) {
|
||||
this.tap.false(val, `expect falsey value: ${msg} - value: ${JSON.stringify(val)}`)
|
||||
return this
|
||||
}
|
||||
function is_false (val, msg) {
|
||||
assert.ok(!val, `expect falsey value: ${msg} - value: ${JSON.stringify(val)}`)
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Asserts that two values are the same
|
||||
* @param {any} the first value
|
||||
* @param {any} the second value
|
||||
* @returns {TestRunner}
|
||||
*/
|
||||
match (val1, val2, action) {
|
||||
function match (val1, val2, action) {
|
||||
// both values are objects
|
||||
if (typeof val1 === 'object' && typeof val2 === 'object') {
|
||||
this.tap.strictDeepEqual(val1, val2, action)
|
||||
assert.ok(deepEqual(val1, val2), action)
|
||||
// the first value is the body as string and the second a pattern string
|
||||
} else if (
|
||||
typeof val1 === 'string' && typeof val2 === 'string' &&
|
||||
@ -672,84 +587,79 @@ class TestRunner {
|
||||
.replace(/\s/g, '')
|
||||
.slice(1, -1)
|
||||
// 'm' adds the support for multiline regex
|
||||
this.tap.match(val1, new RegExp(regStr, 'm'), `should match pattern provided: ${val2}, action: ${JSON.stringify(action)}`)
|
||||
assert.ok(new RegExp(regStr, 'm').test(val1), `should match pattern provided: ${val2}, action: ${JSON.stringify(action)}`)
|
||||
// tap.match(val1, new RegExp(regStr, 'm'), `should match pattern provided: ${val2}, action: ${JSON.stringify(action)}`)
|
||||
// everything else
|
||||
} else {
|
||||
this.tap.strictEqual(val1, val2, `should be equal: ${val1} - ${val2}, action: ${JSON.stringify(action)}`)
|
||||
}
|
||||
return this
|
||||
assert.strictEqual(val1, val2, `should be equal: ${val1} - ${val2}, action: ${JSON.stringify(action)}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Asserts that the first value is less than the second
|
||||
* It also verifies that the two values are numbers
|
||||
* @param {any} the first value
|
||||
* @param {any} the second value
|
||||
* @returns {TestRunner}
|
||||
*/
|
||||
lt (val1, val2) {
|
||||
function lt (val1, val2) {
|
||||
;[val1, val2] = getNumbers(val1, val2)
|
||||
this.tap.true(val1 < val2)
|
||||
return this
|
||||
}
|
||||
assert.ok(val1 < val2)
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Asserts that the first value is greater than the second
|
||||
* It also verifies that the two values are numbers
|
||||
* @param {any} the first value
|
||||
* @param {any} the second value
|
||||
* @returns {TestRunner}
|
||||
*/
|
||||
gt (val1, val2) {
|
||||
function gt (val1, val2) {
|
||||
;[val1, val2] = getNumbers(val1, val2)
|
||||
this.tap.true(val1 > val2)
|
||||
return this
|
||||
}
|
||||
assert.ok(val1 > val2)
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Asserts that the first value is less than or equal the second
|
||||
* It also verifies that the two values are numbers
|
||||
* @param {any} the first value
|
||||
* @param {any} the second value
|
||||
* @returns {TestRunner}
|
||||
*/
|
||||
lte (val1, val2) {
|
||||
function lte (val1, val2) {
|
||||
;[val1, val2] = getNumbers(val1, val2)
|
||||
this.tap.true(val1 <= val2)
|
||||
return this
|
||||
}
|
||||
assert.ok(val1 <= val2)
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Asserts that the first value is greater than or equal the second
|
||||
* It also verifies that the two values are numbers
|
||||
* @param {any} the first value
|
||||
* @param {any} the second value
|
||||
* @returns {TestRunner}
|
||||
*/
|
||||
gte (val1, val2) {
|
||||
*/
|
||||
function gte (val1, val2) {
|
||||
;[val1, val2] = getNumbers(val1, val2)
|
||||
this.tap.true(val1 >= val2)
|
||||
return this
|
||||
}
|
||||
assert.ok(val1 >= val2)
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Asserts that the given value has the specified length
|
||||
* @param {string|object|array} the object to check
|
||||
* @param {number} the expected length
|
||||
* @returns {TestRunner}
|
||||
*/
|
||||
length (val, len) {
|
||||
function length (val, len) {
|
||||
if (typeof val === 'string' || Array.isArray(val)) {
|
||||
this.tap.strictEqual(val.length, len)
|
||||
assert.strictEqual(val.length, len)
|
||||
} else if (typeof val === 'object' && val !== null) {
|
||||
this.tap.strictEqual(Object.keys(val).length, len)
|
||||
assert.strictEqual(Object.keys(val).length, len)
|
||||
} else {
|
||||
this.tap.fail(`length: the given value is invalid: ${val}`)
|
||||
}
|
||||
return this
|
||||
assert.fail(`length: the given value is invalid: ${val}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Gets a `do` action object and returns a structured object,
|
||||
* where the action is the key and the parameter is the value.
|
||||
* Eg:
|
||||
@ -774,7 +684,7 @@ class TestRunner {
|
||||
* @param {object}
|
||||
* @returns {object}
|
||||
*/
|
||||
parseDo (action) {
|
||||
function parseDo (action) {
|
||||
return Object.keys(action).reduce((acc, val) => {
|
||||
switch (val) {
|
||||
case 'catch':
|
||||
@ -826,7 +736,6 @@ class TestRunner {
|
||||
|
||||
return newObj
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseDoError (err, spec) {
|
||||
@ -886,4 +795,83 @@ function getRandomInt (min, max) {
|
||||
return Math.floor(Math.random() * (max - min)) + min
|
||||
}
|
||||
|
||||
module.exports = TestRunner
|
||||
/**
|
||||
* Logs a skip
|
||||
* @param {object} the actions
|
||||
* @returns {TestRunner}
|
||||
*/
|
||||
function skip (action) {
|
||||
if (action.reason && action.version) {
|
||||
// tap.comment(`Skip: ${action.reason} (${action.version})`)
|
||||
} else if (action.features) {
|
||||
// tap.comment(`Skip: ${JSON.stringify(action.features)})`)
|
||||
} else {
|
||||
// tap.comment('Skipped')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides if a test should be skipped
|
||||
* @param {object} the actions
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function shouldSkip (esVersion, action) {
|
||||
var shouldSkip = false
|
||||
// skip based on the version
|
||||
if (action.version) {
|
||||
if (action.version.trim() === 'all') return true
|
||||
const [min, max] = action.version.split('-').map(v => v.trim())
|
||||
// if both `min` and `max` are specified
|
||||
if (min && max) {
|
||||
shouldSkip = semver.satisfies(esVersion, action.version)
|
||||
// if only `min` is specified
|
||||
} else if (min) {
|
||||
shouldSkip = semver.gte(esVersion, min)
|
||||
// if only `max` is specified
|
||||
} else if (max) {
|
||||
shouldSkip = semver.lte(esVersion, max)
|
||||
// something went wrong!
|
||||
} else {
|
||||
throw new Error(`skip: Bad version range: ${action.version}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSkip) return true
|
||||
|
||||
if (action.features) {
|
||||
if (!Array.isArray(action.features)) action.features = [action.features]
|
||||
// returns true if one of the features is not present in the supportedFeatures
|
||||
shouldSkip = !!action.features.filter(f => !~supportedFeatures.indexOf(f)).length
|
||||
}
|
||||
|
||||
if (shouldSkip) return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the array syntax of keys and values
|
||||
* eg: 'hits.hits.1.stuff' to 'hits.hits[1].stuff'
|
||||
* @param {object} the action to update
|
||||
* @returns {obj} the updated action
|
||||
*/
|
||||
// function updateArraySyntax (obj) {
|
||||
// const newObj = {}
|
||||
|
||||
// for (const key in obj) {
|
||||
// const newKey = key.replace(/\.\d{1,}\./g, v => `[${v.slice(1, -1)}].`)
|
||||
// const val = obj[key]
|
||||
|
||||
// if (typeof val === 'string') {
|
||||
// newObj[newKey] = val.replace(/\.\d{1,}\./g, v => `[${v.slice(1, -1)}].`)
|
||||
// } else if (val !== null && typeof val === 'object') {
|
||||
// newObj[newKey] = updateArraySyntax(val)
|
||||
// } else {
|
||||
// newObj[newKey] = val
|
||||
// }
|
||||
// }
|
||||
|
||||
// return newObj
|
||||
// }
|
||||
|
||||
module.exports = build
|
||||
|
||||
Reference in New Issue
Block a user