Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: measure codecov, add missing tests #359

Merged
merged 12 commits into from
Mar 19, 2022
1 change: 1 addition & 0 deletions .gitignore
@@ -1,3 +1,4 @@
/node_modules/
package-lock.json
yarn.lock
coverage
5 changes: 4 additions & 1 deletion package.json
Expand Up @@ -26,7 +26,10 @@
"node": ">= 16.0.0"
},
"scripts": {
"test": "node zx.mjs test.mjs"
"test:cov": "c8 --reporter=html npm run test",
"test": "node zx.mjs test/full.test.mjs",
"test:zx": "npm run test zx",
"test:index": "npm run test index"
antonmedv marked this conversation as resolved.
Show resolved Hide resolved
},
"dependencies": {
"@types/fs-extra": "^9.0.13",
Expand Down
10 changes: 6 additions & 4 deletions src/index.mjs
Expand Up @@ -155,12 +155,14 @@ export async function question(query, options) {
const rl = createInterface({
input: process.stdin,
output: process.stdout,
terminal: true,
completer,
})
const question = (q) => new Promise((resolve) => rl.question(q ?? '', resolve))
let answer = await question(query)
rl.close()
return answer

return new Promise((resolve) => rl.question(query ?? '', (answer) => {
rl.close()
resolve(answer)
}))
}

export async function fetch(url, init) {
Expand Down
60 changes: 60 additions & 0 deletions test/experimental.test.mjs
@@ -0,0 +1,60 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {echo, retry, startSpinner, withTimeout} from '../src/experimental.mjs'
import {assert, test as t} from './test-utils.mjs'
import chalk from 'chalk'

const test = t.bind(null, 'experimental')

if (test('Retry works')) {
let exitCode = 0
let now = Date.now()
try {
await retry(5, 50)`exit 123`
} catch (p) {
exitCode = p.exitCode
}
assert.equal(exitCode, 123)
assert(Date.now() >= now + 50 * (5 - 1))
}

if (test('withTimeout works')) {
let exitCode = 0
let signal
try {
await withTimeout(100, 'SIGKILL')`sleep 9999`
} catch (p) {
exitCode = p.exitCode
signal = p.signal
}
assert.equal(exitCode, null)
assert.equal(signal, 'SIGKILL')

let p = await withTimeout(0)`echo 'test'`
assert.equal(p.stdout.trim(), 'test')
}

if (test('echo works')) {
echo(chalk.red('foo'), chalk.green('bar'), chalk.bold('baz'))
echo`${chalk.red('foo')} ${chalk.green('bar')} ${chalk.bold('baz')}`
echo(await $`echo ${chalk.red('foo')}`, await $`echo ${chalk.green('bar')}`, await $`echo ${chalk.bold('baz')}`)
}

if (test('spinner works')) {
let s = startSpinner('waiting')

await sleep(1000)
s()
}
6 changes: 6 additions & 0 deletions test/fixtures/echo.http
@@ -0,0 +1,6 @@
HTTP/1.1 200 OK
Content-Type: text/javascript; charset=UTF-8
Content-Length: 15
Server: netcat!

$`echo 'test'`
File renamed without changes.
File renamed without changes.
File renamed without changes.
20 changes: 20 additions & 0 deletions test/full.test.mjs
@@ -0,0 +1,20 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {printTestDigest} from './test-utils.mjs'
await import('./zx.test.mjs')
await import('./index.test.mjs')
await import('./experimental.test.mjs')

printTestDigest()
141 changes: 66 additions & 75 deletions test.mjs → test/index.test.mjs
Expand Up @@ -12,20 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {strict as assert} from 'assert'
import {retry, echo, startSpinner, withTimeout } from './src/experimental.mjs'
import {inspect} from 'util'
import chalk from 'chalk'
import {Writable} from 'stream'
import {Socket} from 'net'

let всегоТестов = 0
import {assert, test as t} from './test-utils.mjs'

function test(name) {
let фильтр = process.argv[3] || '.'
if (RegExp(фильтр).test(name)) {
console.log('\n' + chalk.bgGreenBright.black(` ${name} `))
всегоТестов++
return true
}
return false
}
const test = t.bind(null, 'index')

if (test('Only stdout is used during command substitution')) {
let hello = await $`echo Error >&2; echo Hello`
Expand Down Expand Up @@ -86,29 +80,20 @@ if (test('The toString() is called on arguments')) {

if (test('Can use array as an argument')) {
try {
let files = ['./zx.mjs', './test.mjs']
let files = ['./zx.mjs', './test/index.test.mjs']
await $`tar czf archive ${files}`
} finally {
await $`rm archive`
}
}

if (test('Scripts with no extension')) {
await $`node zx.mjs tests/no-extension`
assert.match((await fs.readFile('tests/no-extension.mjs')).toString(), /Test file to verify no-extension didn't overwrite similarly name .mjs file./)
}

if (test('The require() is working from stdin')) {
await $`node zx.mjs <<< 'require("./package.json").name'`
}

if (test('Markdown scripts are working')) {
await $`node zx.mjs docs/markdown.md`
}

if (test('Quiet mode is working')) {
let {stdout} = await $`node zx.mjs --quiet docs/markdown.md`
assert(!stdout.includes('whoami'))
let stdout = ''
let log = console.log
console.log = (...args) => {stdout += args.join(' ')}
await quiet($`echo 'test'`)
console.log = log
assert(!stdout.includes('echo'))
}

if (test('Pipes are working')) {
Expand All @@ -131,6 +116,41 @@ if (test('Pipes are working')) {
}
}

if (test('question')) {
let p = question('foo or bar? ', {choices: ['foo', 'bar']})

setImmediate(() => {
process.stdin.emit('data', 'fo')
process.stdin.emit('data', '\t')
process.stdin.emit('data', '\n')
})

assert.equal(await p, 'foo')
}

if (test('ProcessPromise')) {
let contents = ''
let stream = new Writable({
write: function(chunk, encoding, next) {
contents += chunk.toString()
next()
}
})
let p = $`echo 'test'`.pipe(stream)
await p
assert(p._piped)
assert.equal(contents, 'test\n')
assert(p.stderr instanceof Socket)

let err
try {
$`echo 'test'`.pipe('str')
} catch (p) {
err = p
}
assert.equal(err.message, 'The pipe() method does not take strings. Forgot $?')
}

if (test('ProcessOutput thrown as error')) {
let err
try {
Expand All @@ -139,6 +159,8 @@ if (test('ProcessOutput thrown as error')) {
err = p
}
assert(err.exitCode > 0)
assert(err.stderr.includes('/bin/bash: wtf: command not found\n'))
assert(err[inspect.custom]().includes('Command not found'))
}

if (test('The pipe() throws if already resolved')) {
Expand Down Expand Up @@ -174,6 +196,19 @@ if (test('globby available')) {
assert(typeof globby.isGitIgnored === 'function')
assert(typeof globby.isGitIgnoredSync === 'function')
console.log(chalk.greenBright('globby available'))

assert(await globby('test/fixtures/*'), [
'test/fixtures/interactive.mjs',
'test/fixtures/no-extension',
'test/fixtures/no-extension.mjs'
])
}

if (test('fetch')) {
assert(
await fetch('https://example.com'),
await fetch('https://example.com', {method: 'GET'})
)
}

if (test('Executes a script from $PATH')) {
Expand Down Expand Up @@ -201,6 +236,7 @@ if (test('Executes a script from $PATH')) {
}

if (test('The cd() works with relative paths')) {
let cwd = process.cwd()
try {
fs.mkdirpSync('/tmp/zx-cd-test/one/two')
cd('/tmp/zx-cd-test/one/two')
Expand All @@ -216,7 +252,7 @@ if (test('The cd() works with relative paths')) {
assert.deepEqual(results, ['two', 'one', 'zx-cd-test'])
} finally {
fs.rmSync('/tmp/zx-cd-test', {recursive: true})
cd(__dirname)
cd(cwd)
}
}

Expand Down Expand Up @@ -249,53 +285,8 @@ if (test('which available')) {
assert.equal(which.sync('npm'), await which('npm'))
}

if (test('Retry works (experimental)')) {
let exitCode = 0
let now = Date.now()
try {
await retry(5, 50)`exit 123`
} catch (p) {
exitCode = p.exitCode
}
assert.equal(exitCode, 123)
assert(Date.now() >= now + 50 * (5 - 1))
}

if (test('withTimeout works (experimental)')) {
let exitCode = 0
let signal
try {
await withTimeout(100, 'SIGKILL')`sleep 9999`
} catch (p) {
exitCode = p.exitCode
signal = p.signal
}
assert.equal(exitCode, null)
assert.equal(signal, 'SIGKILL')
}

if (test('echo works (experimental)')) {
echo(chalk.red('foo'), chalk.green('bar'), chalk.bold('baz'))
echo`${chalk.red('foo')} ${chalk.green('bar')} ${chalk.bold('baz')}`
echo(await $`echo ${chalk.red('foo')}`, await $`echo ${chalk.green('bar')}`, await $`echo ${chalk.bold('baz')}`)
}

if (test('spinner works (experimental)')) {
let s = startSpinner('waiting')

await sleep(1000)
s()
}

let version
if (test('require() is working in ESM')) {
let data = require('./package.json')
version = data.version
let data = require('../package.json')
assert.equal(data.name, 'zx')
assert.equal(data, require('zx/package.json'))
}

console.log('\n' +
chalk.black.bgYellowBright(` zx version is ${version} `) + '\n' +
chalk.greenBright(` 🍺 ${всегоТестов} tests passed `)
)
36 changes: 36 additions & 0 deletions test/test-utils.mjs
@@ -0,0 +1,36 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import chalk from 'chalk'

export {strict as assert} from 'assert'

let всегоТестов = 0

export function test(group, name) {
let фильтр = process.argv[3] || '.'
if (RegExp(фильтр).test(name) || RegExp(фильтр).test(group)) {
console.log('\n' + chalk.bgGreenBright.black(`${chalk.inverse(' ' + group + ' ')} ${name} `))
всегоТестов++
return true
}
return false
}

export const printTestDigest = () => {
console.log('\n' +
chalk.black.bgYellowBright(` zx version is ${require('../package.json').version} `) + '\n' +
chalk.greenBright(` 🍺 ${всегоТестов} tests passed `)
)
}