Skip to content

Commit 49ae801

Browse files
authored
ci: Expand integration test runner logic (#24)
* Improve compatibility with REST YAML test format * Dynamic label for integration test steps * Simplify integration test runner hierarchy We were organizing into APIs and files, but we can just run a flat array of test files. * Log failure count for convenience * Use commonjs version of globby * Update naming * Reinsert stray line that got deleted
1 parent 17f2e5d commit 49ae801

File tree

5 files changed

+91
-111
lines changed

5 files changed

+91
-111
lines changed

.buildkite/pipeline.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
steps:
33
- key: integration
4-
label: ":elasticsearch: :javascript: Elasticsearch Serverless Node.js integration tests"
4+
label: ":elasticsearch: :javascript: Elasticsearch Serverless Node.js v{{ matrix.nodejs }} integration tests"
55
agents:
66
provider: "gcp"
77
matrix:

.ci/make.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env bash
22
# ------------------------------------------------------- #
33
#
4-
# Build entry script for elasticsearch-js
4+
# Build entry script for elasticsearch-serverless-js
55
#
66
# Must be called: ./.ci/make.sh <target> <params>
77
#
@@ -33,7 +33,7 @@ VERSION=$2
3333
STACK_VERSION=$VERSION
3434
set -euo pipefail
3535

36-
product="elastic/elasticsearch-js"
36+
product="elastic/elasticsearch-serverless-js"
3737
output_folder=".ci/output"
3838
codegen_folder=".ci/output"
3939
OUTPUT_DIR="$repo/${output_folder}"
@@ -148,7 +148,7 @@ if [[ -z "${BUILDKITE+x}" ]] || [[ -z "${CI+x}" ]]; then
148148
--volume "$repo:/usr/src/app" \
149149
--volume "$(realpath $repo/../elastic-client-generator-js):/usr/src/elastic-client-generator-js" \
150150
--env "WORKFLOW=$WORKFLOW" \
151-
--name make-elasticsearch-js \
151+
--name make-elasticsearch-serverless-js \
152152
--rm \
153153
$product \
154154
/bin/bash -c "mkdir -p /usr/src/elastic-client-generator-js/output && \
@@ -159,7 +159,7 @@ else
159159
--volume "$repo:/usr/src/app" \
160160
--volume /usr/src/app/node_modules \
161161
--env "WORKFLOW=$WORKFLOW" \
162-
--name make-elasticsearch-js \
162+
--name make-elasticsearch-serverless-js \
163163
--rm \
164164
$product \
165165
/bin/bash -c "cd /usr/src && \

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"chai": "^4.3.10",
6060
"cross-zip": "^4.0.0",
6161
"desm": "^1.3.0",
62+
"globby": "^11.1.0",
6263
"js-yaml": "^4.1.0",
6364
"license-checker": "^25.0.1",
6465
"node-fetch": "^2.7.0",

test/integration/index.js

Lines changed: 77 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const { join, sep } = require('path')
2929
const yaml = require('js-yaml')
3030
const minimist = require('minimist')
3131
const ms = require('ms')
32+
const globby = require('globby')
3233
const { Client } = require('../../index')
3334
const build = require('./test-runner')
3435
const createJunitReporter = require('./reporter')
@@ -45,18 +46,13 @@ const options = minimist(process.argv.slice(2), {
4546
string: ['suite', 'test'],
4647
})
4748

48-
const getAllFiles = dir => {
49-
return readdirSync(dir)
50-
.reduce((files, file) => {
51-
const name = join(dir, file)
52-
if (statSync(name).isDirectory()) {
53-
return [...files, ...getAllFiles(name)]
54-
} else if (!name.endsWith('.yaml') && !name.endsWith('.yml')) {
55-
return files
56-
} else {
57-
return [...files, name]
58-
}
59-
}, [])
49+
const getAllFiles = async dir => {
50+
const files = await globby(dir, {
51+
expandDirectories: {
52+
extensions: ['yml', 'yaml']
53+
}
54+
})
55+
return files.sort()
6056
}
6157

6258
function runner (opts = {}) {
@@ -89,112 +85,88 @@ async function start ({ client }) {
8985
pass: 0,
9086
assertions: 0
9187
}
92-
const folders = getAllFiles(yamlFolder)
93-
.reduce((arr, file) => {
94-
const path = file.slice(file.indexOf('/tests'), file.lastIndexOf('/'))
95-
let inserted = false
96-
for (let i = 0; i < arr.length; i++) {
97-
if (arr[i][0].includes(path)) {
98-
inserted = true
99-
arr[i].push(file)
100-
break
101-
}
102-
}
103-
if (!inserted) arr.push([file])
104-
return arr
105-
}, [])
88+
const files = await getAllFiles(yamlFolder)
10689

10790
const totalTime = now()
108-
for (const folder of folders) {
91+
for (const file of files) {
10992
// pretty name
110-
const apiName = folder[0].split(`${sep}tests${sep}`)[1]
93+
const apiName = file.split(`${sep}tests${sep}`)[1]
11194

11295
log('Testing ' + apiName)
113-
const apiTime = now()
114-
115-
for (const file of folder) {
116-
const testRunner = build({ client })
117-
const fileTime = now()
118-
const data = readFileSync(file, 'utf8')
119-
120-
// get the test yaml (as object), some file has multiple yaml documents inside,
121-
// every document is separated by '---', so we split on the separator
122-
// and then we remove the empty strings, finally we parse them
123-
const tests = data
124-
.split('\n---\n')
125-
.map(s => s.trim())
126-
// empty strings
127-
.filter(Boolean)
128-
.map(parse)
129-
// null values
130-
.filter(Boolean)
131-
132-
// get setup and teardown if present
133-
let setupTest = null
134-
let teardownTest = null
135-
for (const test of tests) {
136-
if (test.setup) setupTest = test.setup
137-
if (test.teardown) teardownTest = test.teardown
138-
}
96+
const testRunner = build({ client })
97+
const fileTime = now()
98+
const data = readFileSync(file, 'utf8')
99+
100+
// get the test yaml (as object), some file has multiple yaml documents inside,
101+
// every document is separated by '---', so we split on the separator
102+
// and then we remove the empty strings, finally we parse them
103+
const tests = data
104+
.split('\n---\n')
105+
.map(s => s.trim())
106+
// empty strings
107+
.filter(Boolean)
108+
.map(parse)
109+
// null values
110+
.filter(Boolean)
111+
112+
// get setup and teardown if present
113+
let setupTest = null
114+
let teardownTest = null
115+
for (const test of tests) {
116+
if (test.setup) setupTest = test.setup
117+
if (test.teardown) teardownTest = test.teardown
118+
}
139119

140-
const cleanPath = file.slice(file.lastIndexOf(apiName))
141-
142-
// skip if --suite CLI arg doesn't match
143-
if (options.suite && !cleanPath.endsWith(options.suite)) continue
144-
145-
log(' ' + cleanPath)
146-
const junitTestSuite = junitTestSuites.testsuite(apiName.slice(1) + ' - ' + cleanPath)
147-
148-
for (const test of tests) {
149-
const testTime = now()
150-
const name = Object.keys(test)[0]
151-
152-
// skip setups, teardowns and anything that doesn't match --test flag when present
153-
if (name === 'setup' || name === 'teardown') continue
154-
if (options.test && !name.endsWith(options.test)) continue
155-
156-
const junitTestCase = junitTestSuite.testcase(name, `node_${process.version}/${cleanPath}`)
157-
158-
stats.total += 1
159-
log(' - ' + name)
160-
try {
161-
await testRunner.run(setupTest, test[name], teardownTest, stats, junitTestCase)
162-
stats.pass += 1
163-
} catch (err) {
164-
junitTestCase.failure(err)
165-
junitTestCase.end()
166-
junitTestSuite.end()
167-
junitTestSuites.end()
168-
generateJunitXmlReport(junit, 'serverless')
169-
console.error(err)
170-
171-
if (options.bail) {
172-
process.exit(1)
173-
} else {
174-
continue
175-
}
176-
}
177-
const totalTestTime = now() - testTime
120+
const cleanPath = file.slice(file.lastIndexOf(apiName))
121+
122+
// skip if --suite CLI arg doesn't match
123+
if (options.suite && !cleanPath.endsWith(options.suite)) continue
124+
125+
const junitTestSuite = junitTestSuites.testsuite(apiName.slice(1) + ' - ' + cleanPath)
126+
127+
for (const test of tests) {
128+
const testTime = now()
129+
const name = Object.keys(test)[0]
130+
131+
// skip setups, teardowns and anything that doesn't match --test flag when present
132+
if (name === 'setup' || name === 'teardown') continue
133+
if (options.test && !name.endsWith(options.test)) continue
134+
135+
const junitTestCase = junitTestSuite.testcase(name, `node_${process.version}/${cleanPath}`)
136+
137+
stats.total += 1
138+
log(' - ' + name)
139+
try {
140+
await testRunner.run(setupTest, test[name], teardownTest, stats, junitTestCase)
141+
stats.pass += 1
142+
} catch (err) {
143+
junitTestCase.failure(err)
178144
junitTestCase.end()
179-
if (totalTestTime > MAX_TEST_TIME) {
180-
log(' took too long: ' + ms(totalTestTime))
145+
junitTestSuite.end()
146+
junitTestSuites.end()
147+
generateJunitXmlReport(junit, 'serverless')
148+
console.error(err)
149+
150+
if (options.bail) {
151+
process.exit(1)
181152
} else {
182-
log(' took: ' + ms(totalTestTime))
153+
continue
183154
}
184155
}
185-
junitTestSuite.end()
186-
const totalFileTime = now() - fileTime
187-
if (totalFileTime > MAX_FILE_TIME) {
188-
log(` ${cleanPath} took too long: ` + ms(totalFileTime))
156+
const totalTestTime = now() - testTime
157+
junitTestCase.end()
158+
if (totalTestTime > MAX_TEST_TIME) {
159+
log(' took too long: ' + ms(totalTestTime))
189160
} else {
190-
log(` ${cleanPath} took: ` + ms(totalFileTime))
161+
log(' took: ' + ms(totalTestTime))
191162
}
192163
}
193-
const totalApiTime = now() - apiTime
194-
if (totalApiTime > MAX_API_TIME) {
195-
log(`${apiName} took too long: ` + ms(totalApiTime))
164+
junitTestSuite.end()
165+
const totalFileTime = now() - fileTime
166+
if (totalFileTime > MAX_FILE_TIME) {
167+
log(` ${cleanPath} took too long: ` + ms(totalFileTime))
196168
} else {
197-
log(`${apiName} took: ` + ms(totalApiTime))
169+
log(` ${cleanPath} took: ` + ms(totalFileTime))
198170
}
199171
}
200172
junitTestSuites.end()
@@ -204,6 +176,7 @@ async function start ({ client }) {
204176
- Total: ${stats.total}
205177
- Skip: ${stats.skip}
206178
- Pass: ${stats.pass}
179+
- Fail: ${stats.total - stats.pass}
207180
- Assertions: ${stats.assertions}
208181
`)
209182
}

test/integration/test-runner.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,10 +420,10 @@ function build (opts = {}) {
420420
const key = Object.keys(action.match)[0]
421421
match(
422422
// in some cases, the yaml refers to the body with an empty string
423-
key === '$body' || key === ''
423+
key.split('.')[0] === '$body' || key === ''
424424
? response
425425
: delve(response, fillStashedValues(key)),
426-
key === '$body'
426+
key.split('.')[0] === '$body'
427427
? action.match[key]
428428
: fillStashedValues(action.match)[key],
429429
action.match
@@ -544,6 +544,8 @@ function match (val1, val2, action) {
544544
// 'm' adds the support for multiline regex
545545
assert.match(val1, new RegExp(regStr, 'm'), `should match pattern provided: ${val2}, but got: ${val1}`)
546546
// everything else
547+
} else if (typeof val1 === 'string' && typeof val2 === 'string') {
548+
assert.include(val1, val2, `should match pattern provided: ${val2}, but got: ${val1}`)
547549
} else {
548550
assert.equal(val1, val2, `should be equal: ${val1} - ${val2}, action: ${JSON.stringify(action)}`)
549551
}
@@ -640,6 +642,10 @@ function length (val, len) {
640642
*/
641643
function parseDo (action) {
642644
action = JSON.parse(JSON.stringify(action))
645+
646+
if (typeof action === 'string') action = {[action]: {}}
647+
if (Array.isArray(action)) action = action[0]
648+
643649
return Object.keys(action).reduce((acc, val) => {
644650
switch (val) {
645651
case 'catch':

0 commit comments

Comments
 (0)