Commit 23453bd

Anton Golub <antongolub@antongolub.com>
2022-05-30 10:48:50
test: replace ava with uvu
1 parent 90005ea
src/core.ts
@@ -21,10 +21,12 @@ import {
 import { Readable, Writable } from 'node:stream'
 import { inspect, promisify } from 'node:util'
 import { spawn } from 'node:child_process'
+
 import { chalk, which } from './goods.js'
-import { getCtx, runInCtx, setRootCtx } from './context.js'
+import { runInCtx, getCtx, setRootCtx } from './context.js'
 import { printStd, printCmd } from './print.js'
 import { quote, substitute } from './guards.js'
+
 import psTreeModule from 'ps-tree'
 
 const psTree = promisify(psTreeModule)
test/cli.test.js
@@ -12,55 +12,55 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import test from 'ava'
+import { test } from 'uvu'
+import * as assert from 'uvu/assert'
 import '../build/globals.js'
 
 $.verbose = false
 
-test('supports `-v` flag / prints version', async (t) => {
-  t.regex((await $`node build/cli.js -v`).toString(), /\d+.\d+.\d+/)
+test('supports `-v` flag / prints version', async () => {
+  assert.match((await $`node build/cli.js -v`).toString(), /\d+.\d+.\d+/)
 })
 
-test('prints help', async (t) => {
+test('prints help', async () => {
   let help
   try {
     await $`node build/cli.js`
   } catch (err) {
     help = err.toString().trim()
   }
-  t.true(help.includes('print current zx version'))
+  assert.ok(help.includes('print current zx version'))
 })
 
-test('supports `--experimental` flag', async (t) => {
+test('supports `--experimental` flag', async () => {
   await $`echo 'echo("test")' | node build/cli.js --experimental`
-  t.pass()
 })
 
-test('supports `--quiet` flag / Quiet mode is working', async (t) => {
+test('supports `--quiet` flag / Quiet mode is working', async () => {
   let p = await $`node build/cli.js --quiet docs/markdown.md`
-  t.true(!p.stdout.includes('whoami'))
+  assert.ok(!p.stdout.includes('whoami'))
 })
 
-test('supports `--shell` flag ', async (t) => {
+test('supports `--shell` flag ', async () => {
   let shell = $.shell
   let p =
     await $`node build/cli.js --shell=${shell} <<< '$\`echo \${$.shell}\`'`
-  t.true(p.stdout.includes(shell))
+  assert.ok(p.stdout.includes(shell))
 })
 
-test('supports `--prefix` flag ', async (t) => {
+test('supports `--prefix` flag ', async () => {
   let prefix = 'set -e;'
   let p =
     await $`node build/cli.js --prefix=${prefix} <<< '$\`echo \${$.prefix}\`'`
-  t.true(p.stdout.includes(prefix))
+  assert.ok(p.stdout.includes(prefix))
 })
 
-test('scripts from https', async (t) => {
+test('scripts from https', async () => {
   let script = path.resolve('test/fixtures/echo.http')
   let server = quiet($`while true; do cat ${script} | nc -l 8080; done`)
   let p = await quiet($`node build/cli.js http://127.0.0.1:8080/echo.mjs`)
 
-  t.true(p.stdout.includes('test'))
+  assert.ok(p.stdout.includes('test'))
   server.kill()
 
   let err
@@ -69,34 +69,32 @@ test('scripts from https', async (t) => {
   } catch (e) {
     err = e
   }
-  t.true(err.stderr.includes('ECONNREFUSED'))
+  assert.ok(err.stderr.includes('ECONNREFUSED'))
 })
 
-test('scripts with no extension', async (t) => {
+test('scripts with no extension', async () => {
   await $`node build/cli.js test/fixtures/no-extension`
-  t.true(
+  assert.ok(
     /Test file to verify no-extension didn't overwrite similarly name .mjs file./.test(
       (await fs.readFile('test/fixtures/no-extension.mjs')).toString()
     )
   )
 })
 
-test('require() is working from stdin', async (t) => {
+test('require() is working from stdin', async () => {
   await $`node build/cli.js <<< 'require("./package.json").name'`
-  t.pass()
 })
 
-test('require() is working in ESM', async (t) => {
+test('require() is working in ESM', async () => {
   await $`node build/cli.js test/fixtures/require.mjs`
-  t.pass()
 })
 
-test('__filename & __dirname are defined', async (t) => {
+test('__filename & __dirname are defined', async () => {
   await $`node build/cli.js test/fixtures/filename-dirname.mjs`
-  t.pass()
 })
 
-test('markdown scripts are working', async (t) => {
+test('markdown scripts are working', async () => {
   await $`node build/cli.js docs/markdown.md`
-  t.pass()
 })
+
+test.run()
test/experimental.test.js
@@ -12,11 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import test from 'ava'
+import { test } from 'uvu'
+import * as assert from 'uvu/assert'
 import '../build/globals.js'
 
-$.verbose = false
-
 import {
   echo,
   retry,
@@ -24,7 +23,11 @@ import {
   withTimeout,
 } from '../build/experimental.js'
 
-test('retry works', async (t) => {
+import chalk from 'chalk'
+
+$.verbose = false
+
+test('retry works', async () => {
   let exitCode = 0
   let now = Date.now()
   try {
@@ -32,11 +35,11 @@ test('retry works', async (t) => {
   } catch (p) {
     exitCode = p.exitCode
   }
-  t.is(exitCode, 123)
-  t.true(Date.now() >= now + 50 * (5 - 1))
+  assert.is(exitCode, 123)
+  assert.ok(Date.now() >= now + 50 * (5 - 1))
 })
 
-test('withTimeout works', async (t) => {
+test('withTimeout works', async () => {
   let exitCode = 0
   let signal
   try {
@@ -45,14 +48,14 @@ test('withTimeout works', async (t) => {
     exitCode = p.exitCode
     signal = p.signal
   }
-  t.is(exitCode, null)
-  t.is(signal, 'SIGKILL')
+  assert.is(exitCode, null)
+  assert.is(signal, 'SIGKILL')
 
   let p = await withTimeout(0)`echo 'test'`
-  t.is(p.stdout.trim(), 'test')
+  assert.is(p.stdout.trim(), 'test')
 })
 
-test('echo works', async (t) => {
+test('echo works', async () => {
   echo(chalk.cyan('foo'), chalk.green('bar'), chalk.bold('baz'))
   echo`${chalk.cyan('foo')} ${chalk.green('bar')} ${chalk.bold('baz')}`
   echo(
@@ -60,12 +63,12 @@ test('echo works', async (t) => {
     await $`echo ${chalk.green('bar')}`,
     await $`echo ${chalk.bold('baz')}`
   )
-  t.pass()
 })
 
-test('spinner works', async (t) => {
+test('spinner works', async () => {
   let s = startSpinner('waiting')
   await sleep(1000)
   s()
-  t.pass()
 })
+
+test.run()
test/index.test.js
@@ -12,7 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import test from 'ava'
+import { test } from 'uvu'
+import * as assert from 'uvu/assert'
 import { inspect } from 'node:util'
 import chalk from 'chalk'
 import { Writable } from 'node:stream'
@@ -20,48 +21,46 @@ import { Socket } from 'node:net'
 import '../build/globals.js'
 import { ProcessPromise } from '../build/index.js'
 
-$.verbose = false
-
-test('only stdout is used during command substitution', async (t) => {
+test('only stdout is used during command substitution', async () => {
   let hello = await $`echo Error >&2; echo Hello`
   let len = +(await $`echo ${hello} | wc -c`)
-  t.is(len, 6)
+  assert.is(len, 6)
 })
 
-test('env vars works', async (t) => {
+test('env vars works', async () => {
   process.env.ZX_TEST_FOO = 'foo'
   let foo = await $`echo $ZX_TEST_FOO`
-  t.is(foo.stdout, 'foo\n')
+  assert.is(foo.stdout, 'foo\n')
 })
 
-test('env vars is safe to pass', async (t) => {
+test('env vars is safe to pass', async () => {
   process.env.ZX_TEST_BAR = 'hi; exit 1'
   await $`echo $ZX_TEST_BAR`
-  t.pass()
 })
 
-test('arguments are quoted', async (t) => {
+test('arguments are quoted', async () => {
   let bar = 'bar"";baz!$#^$\'&*~*%)({}||\\/'
-  t.is((await $`echo ${bar}`).stdout.trim(), bar)
+  assert.is((await $`echo ${bar}`).stdout.trim(), bar)
 })
 
-test('undefined and empty string correctly quoted', async (t) => {
-  t.verbose = true
-  t.is((await $`echo -n ${undefined}`).toString(), 'undefined')
-  t.is((await $`echo -n ${''}`).toString(), '')
+test('undefined and empty string correctly quoted', async () => {
+  $.verbose = true
+  assert.is((await $`echo -n ${undefined}`).toString(), 'undefined')
+  assert.is((await $`echo -n ${''}`).toString(), '')
 })
 
-test('can create a dir with a space in the name', async (t) => {
+test('can create a dir with a space in the name', async () => {
   let name = 'foo bar'
   try {
     await $`mkdir /tmp/${name}`
+  } catch {
+    assert.unreachable()
   } finally {
     await fs.rmdir('/tmp/' + name)
   }
-  t.pass()
 })
 
-test('pipefail is on', async (t) => {
+test('pipefail is on', async () => {
   let p
   try {
     p = await $`cat /dev/not_found | sort`
@@ -69,21 +68,21 @@ test('pipefail is on', async (t) => {
     console.log('Caught an exception -> ok')
     p = e
   }
-  t.not(p.exitCode, 0)
+  assert.is.not(p.exitCode, 0)
 })
 
-test('toString() is called on arguments', async (t) => {
+test('toString() is called on arguments', async () => {
   let foo = 0
   let p = await $`echo ${foo}`
-  t.is(p.stdout, '0\n')
+  assert.is(p.stdout, '0\n')
 })
 
-test('can use array as an argument', async (t) => {
+test('can use array as an argument', async () => {
   let args = ['-n', 'foo']
-  t.is((await $`echo ${args}`).toString(), 'foo')
+  assert.is((await $`echo ${args}`).toString(), 'foo')
 })
 
-test.serial('quiet mode is working', async (t) => {
+test('quiet mode is working', async () => {
   let stdout = ''
   let log = console.log
   console.log = (...args) => {
@@ -91,28 +90,28 @@ test.serial('quiet mode is working', async (t) => {
   }
   await quiet($`echo 'test'`)
   console.log = log
-  t.is(stdout, '')
+  assert.is(stdout, '')
 })
 
-test('pipes are working', async (t) => {
+test('pipes are working', async () => {
   let { stdout } = await $`echo "hello"`
     .pipe($`awk '{print $1" world"}'`)
     .pipe($`tr '[a-z]' '[A-Z]'`)
-  t.is(stdout, 'HELLO WORLD\n')
+  assert.is(stdout, 'HELLO WORLD\n')
 
   try {
     await $`echo foo`.pipe(fs.createWriteStream('/tmp/output.txt'))
-    t.is((await fs.readFile('/tmp/output.txt')).toString(), 'foo\n')
+    assert.is((await fs.readFile('/tmp/output.txt')).toString(), 'foo\n')
 
     let r = $`cat`
     fs.createReadStream('/tmp/output.txt').pipe(r.stdin)
-    t.is((await r).stdout, 'foo\n')
+    assert.is((await r).stdout, 'foo\n')
   } finally {
     await fs.rm('/tmp/output.txt')
   }
 })
 
-test('question', async (t) => {
+test('question', async () => {
   let p = question('foo or bar? ', { choices: ['foo', 'bar'] })
 
   setImmediate(() => {
@@ -121,10 +120,10 @@ test('question', async (t) => {
     process.stdin.emit('data', '\n')
   })
 
-  t.is(await p, 'foo')
+  assert.is(await p, 'foo')
 })
 
-test('ProcessPromise', async (t) => {
+test('ProcessPromise', async () => {
   let contents = ''
   let stream = new Writable({
     write: function (chunk, encoding, next) {
@@ -134,9 +133,9 @@ test('ProcessPromise', async (t) => {
   })
   let p = $`echo 'test'`.pipe(stream)
   await p
-  t.true(p._piped)
-  t.is(contents, 'test\n')
-  t.true(p.stderr instanceof Socket)
+  assert.ok(p._piped)
+  assert.is(contents, 'test\n')
+  assert.ok(p.stderr instanceof Socket)
 
   let err
   try {
@@ -144,38 +143,38 @@ test('ProcessPromise', async (t) => {
   } catch (p) {
     err = p
   }
-  t.is(err.message, 'The pipe() method does not take strings. Forgot $?')
+  assert.is(err.message, 'The pipe() method does not take strings. Forgot $?')
 })
 
-test('ProcessPromise: inherits native Promise', async (t) => {
+test('ProcessPromise: inherits native Promise', async () => {
   const p1 = $`echo 1`
   const p2 = p1.then((v) => v)
   const p3 = p2.then((v) => v)
   const p4 = p3.catch((v) => v)
   const p5 = p1.finally((v) => v)
 
-  t.true(p1 instanceof Promise)
-  t.true(p1 instanceof ProcessPromise)
-  t.true(p2 instanceof ProcessPromise)
-  t.true(p3 instanceof ProcessPromise)
-  t.true(p4 instanceof ProcessPromise)
-  t.true(p5 instanceof ProcessPromise)
-  t.true(p1 !== p2)
-  t.true(p2 !== p3)
-  t.true(p3 !== p4)
-  t.true(p5 !== p1)
+  assert.ok(p1 instanceof Promise)
+  assert.ok(p1 instanceof ProcessPromise)
+  assert.ok(p2 instanceof ProcessPromise)
+  assert.ok(p3 instanceof ProcessPromise)
+  assert.ok(p4 instanceof ProcessPromise)
+  assert.ok(p5 instanceof ProcessPromise)
+  assert.ok(p1 !== p2)
+  assert.ok(p2 !== p3)
+  assert.ok(p3 !== p4)
+  assert.ok(p5 !== p1)
 })
 
-test('ProcessOutput thrown as error', async (t) => {
+test('ProcessOutput thrown as error', async () => {
   let err
   try {
     await $`wtf`
   } catch (p) {
     err = p
   }
-  t.true(err.exitCode > 0)
-  t.true(err.stderr.includes('/bin/bash: wtf: command not found\n'))
-  t.true(err[inspect.custom]().includes('Command not found'))
+  assert.ok(err.exitCode > 0)
+  assert.ok(err.stderr.includes('/bin/bash: wtf: command not found\n'))
+  assert.ok(err[inspect.custom]().includes('Command not found'))
 })
 
 test('pipe() throws if already resolved', async (t) => {
@@ -185,7 +184,7 @@ test('pipe() throws if already resolved', async (t) => {
   try {
     out = await p.pipe($`less`)
   } catch (err) {
-    t.is(
+    assert.is(
       err.message,
       `The pipe() method shouldn't be called after promise is already resolved!`
     )
@@ -195,38 +194,38 @@ test('pipe() throws if already resolved', async (t) => {
   }
 })
 
-test('await $`cmd`.exitCode does not throw', async (t) => {
-  t.not(await $`grep qwerty README.md`.exitCode, 0)
-  t.is(await $`[[ -f README.md ]]`.exitCode, 0)
+test('await $`cmd`.exitCode does not throw', async () => {
+  assert.is.not(await $`grep qwerty README.md`.exitCode, 0)
+  assert.is(await $`[[ -f README.md ]]`.exitCode, 0)
 })
 
-test('nothrow() do not throw', async (t) => {
+test('nothrow() do not throw', async () => {
   let { exitCode } = await nothrow($`exit 42`)
-  t.is(exitCode, 42)
+  assert.is(exitCode, 42)
 })
 
-test('globby available', async (t) => {
-  t.is(globby, glob)
-  t.is(typeof globby, 'function')
-  t.is(typeof globby.globbySync, 'function')
-  t.is(typeof globby.globbyStream, 'function')
-  t.is(typeof globby.generateGlobTasks, 'function')
-  t.is(typeof globby.isDynamicPattern, 'function')
-  t.is(typeof globby.isGitIgnored, 'function')
-  t.is(typeof globby.isGitIgnoredSync, 'function')
+test('globby available', async () => {
+  assert.is(globby, glob)
+  assert.is(typeof globby, 'function')
+  assert.is(typeof globby.globbySync, 'function')
+  assert.is(typeof globby.globbyStream, 'function')
+  assert.is(typeof globby.generateGlobTasks, 'function')
+  assert.is(typeof globby.isDynamicPattern, 'function')
+  assert.is(typeof globby.isGitIgnored, 'function')
+  assert.is(typeof globby.isGitIgnoredSync, 'function')
   console.log(chalk.greenBright('globby available'))
 
-  t.deepEqual(await globby('*.md'), ['README.md'])
+  assert.equal(await globby('*.md'), ['README.md'])
 })
 
-test('fetch', async (t) => {
-  t.regex(
+test('fetch', async () => {
+  assert.match(
     await fetch('https://medv.io').then((res) => res.text()),
     /Anton Medvedev/
   )
 })
 
-test('executes a script from $PATH', async (t) => {
+test('executes a script from $PATH', async () => {
   const isWindows = process.platform === 'win32'
   const oldPath = process.env.PATH
 
@@ -249,33 +248,30 @@ test('executes a script from $PATH', async (t) => {
     process.env.PATH = oldPath
     fs.rmSync('/tmp/script-from-path')
   }
-  t.pass()
 })
 
-test('cd() works with relative paths', async (t) => {
+test('cd() works with relative paths', async () => {
   await $`node build/cli.js test/fixtures/cd-relative-paths.mjs`
-  t.pass()
 })
 
-test('cd() does not affect parallel contexts', async (t) => {
+test('cd() does not affect parallel contexts', async () => {
   await $`node build/cli.js test/fixtures/cd-parallel-contexts.mjs`
-  t.pass()
 })
 
-test('kill() method works', async (t) => {
+test('kill() method works', async () => {
   await $`node build/cli.js test/fixtures/kill.mjs`
-  t.pass()
 })
 
-test('a signal is passed with kill() method', async (t) => {
+test('a signal is passed with kill() method', async () => {
   await $`node build/cli.js test/fixtures/kill-signal.mjs`
-  t.pass()
 })
 
-test('YAML works', async (t) => {
-  t.deepEqual(YAML.parse(YAML.stringify({ foo: 'bar' })), { foo: 'bar' })
+test('YAML works', async () => {
+  assert.equal(YAML.parse(YAML.stringify({ foo: 'bar' })), { foo: 'bar' })
 })
 
-test('which available', async (t) => {
-  t.is(which.sync('npm'), await which('npm'))
+test('which available', async () => {
+  assert.is(which.sync('npm'), await which('npm'))
 })
+
+test.run()
package.json
@@ -6,11 +6,26 @@
   "main": "build/index.js",
   "types": "build/index.d.ts",
   "exports": {
-    ".": "./build/index.js",
-    "./globals": "./build/globals.js",
-    "./experimental": "./build/experimental.js",
-    "./cli": "./build/cli.js",
-    "./core": "./build/core.js",
+    ".": {
+      "import": "./build/index.js",
+      "types": "./build/index.d.ts"
+    },
+    "./globals": {
+      "import": "./build/globals.js",
+      "types": "./build/globals.d.ts"
+    },
+    "./experimental": {
+      "import": "./build/experimental.js",
+      "types": "./build/experimental.d.ts"
+    },
+    "./cli": {
+      "import": "./zx.js",
+      "types": "./zx.d.ts"
+    },
+    "./core": {
+      "import": "./build/core.js",
+      "types": "./build/core.d.ts"
+    },
     "./package.json": "./package.json"
   },
   "bin": {
@@ -22,8 +37,12 @@
   "scripts": {
     "fmt": "prettier --write .",
     "build": "tsc",
-    "test": "tsc && ava",
-    "coverage": "c8 --reporter=html npm test"
+    "test": "tsc && npm run test:unit",
+    "coverage": "c8 --reporter=html npm test",
+    "test:unit": "uvu test -i fixtures",
+    "test:cov": "c8 --reporter=html npm run test:unit",
+    "test:zx": "npm run test zx",
+    "test:index": "npm run test index"
   },
   "dependencies": {
     "@types/fs-extra": "^9.0.13",
@@ -31,7 +50,6 @@
     "@types/node": "^17.0",
     "@types/ps-tree": "^1.1.2",
     "@types/which": "^2.0.1",
-    "ava": "^4.2.0",
     "chalk": "^5.0.1",
     "fs-extra": "^10.1.0",
     "globby": "^13.1.1",
@@ -44,15 +62,15 @@
   "devDependencies": {
     "c8": "^7.11.3",
     "prettier": "^2.6.2",
-    "typescript": "^4.8.0-dev.20220529"
+    "typescript": "^4.8.0-dev.20220529",
+    "uvu": "^0.5.3"
   },
   "publishConfig": {
     "registry": "https://wombat-dressing-room.appspot.com"
   },
   "files": [
-    "src",
-    "*.mjs",
-    "*.d.ts"
+    "build",
+    "zx.js"
   ],
   "prettier": {
     "semi": false,
tsconfig.json
@@ -2,6 +2,7 @@
   "compilerOptions": {
     "target": "ES2021",
     "lib": ["ES2021"],
+    "moduleResolution": "nodenext",
     "module": "nodenext",
     "strict": true,
     "noImplicitReturns": true,