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

Add doctor test for bun #1403

Merged
merged 1 commit into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions test/bun/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import * as bun from '../../src/package-managers/bun'
import chaiSetup from '../helpers/chaiSetup'
import { testFail, testPass } from '../helpers/doctorHelpers'
import stubVersions from '../helpers/stubVersions'

chaiSetup()

const mockNpmVersions = {
emitter20: '2.0.0',
'ncu-test-return-version': '2.0.0',
'ncu-test-tag': '1.1.0',
'ncu-test-v2': '2.0.0',
}

describe('bun', function () {
it('list', async () => {
const result = await bun.list({ cwd: __dirname })
Expand All @@ -13,4 +22,15 @@ describe('bun', function () {
const { version } = await bun.latest('ncu-test-v2', '1.0.0', { cwd: __dirname })
version!.should.equal('2.0.0')
})

describe('doctor', function () {
this.timeout(3 * 60 * 1000)

let stub: { restore: () => void }
before(() => (stub = stubVersions(mockNpmVersions, { spawn: true })))
after(() => stub.restore())

testPass({ packageManager: 'bun' })
testFail({ packageManager: 'bun' })
})
})
152 changes: 1 addition & 151 deletions test/doctor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import path from 'path'
import spawn from 'spawn-please'
import { cliOptionsMap } from '../src/cli-options'
import { chalkInit } from '../src/lib/chalk'
import { PackageManagerName } from '../src/types/PackageManagerName'
import chaiSetup from './helpers/chaiSetup'
import { testFail, testPass } from './helpers/doctorHelpers'
import stubVersions from './helpers/stubVersions'

chaiSetup()
Expand All @@ -30,150 +30,6 @@ const ncu = async (
return stdout
}

/** Assertions for npm or yarn when tests pass. */
const testPass = ({ packageManager }: { packageManager: PackageManagerName }) => {
it('upgrade dependencies when tests pass', async function () {
// use dynamic import for ESM module
const { default: stripAnsi } = await import('strip-ansi')
const cwd = path.join(doctorTests, 'pass')
const pkgPath = path.join(cwd, 'package.json')
const nodeModulesPath = path.join(cwd, 'node_modules')
const lockfilePath = path.join(
cwd,
packageManager === 'yarn'
? 'yarn.lock'
: packageManager === 'pnpm'
? 'pnpm-lock.yaml'
: packageManager === 'bun'
? 'bun.lockb'
: 'package-lock.json',
)
const pkgOriginal = await fs.readFile(path.join(cwd, 'package.json'), 'utf-8')
let stdout = ''
let stderr = ''

// touch yarn.lock
// yarn.lock is necessary otherwise yarn sees the package.json in the npm-check-updates directory and throws an error.
if (packageManager === 'yarn' || packageManager === 'bun') {
await fs.writeFile(lockfilePath, '')
}

try {
// explicitly set packageManager to avoid auto yarn detection
await ncu(
['--doctor', '-u', '-p', packageManager],
{
stdout: function (data: string) {
stdout += data
},
stderr: function (data: string) {
stderr += data
},
},
{ cwd },
)
} catch (e) {}

const pkgUpgraded = await fs.readFile(pkgPath, 'utf-8')

// cleanup before assertions in case they fail
await fs.writeFile(pkgPath, pkgOriginal)
await fs.rm(nodeModulesPath, { recursive: true, force: true })
await fs.rm(lockfilePath, { recursive: true, force: true })

// delete yarn cache
if (packageManager === 'yarn') {
await fs.rm(path.join(cwd, '.yarn'), { recursive: true, force: true })
await fs.rm(path.join(cwd, '.pnp.js'), { recursive: true, force: true })
}

// bun prints the run header to stderr instead of stdout
if (packageManager === 'bun') {
stripAnsi(stderr).should.equal('$ echo Success\n\n$ echo Success\n\n')
} else {
stderr.should.equal('')
}

// stdout should include normal output
stripAnsi(stdout).should.containIgnoreCase('Tests pass')
stripAnsi(stdout).should.containIgnoreCase('ncu-test-v2 ~1.0.0 → ~2.0.0')

// package file should include upgrades
pkgUpgraded.should.containIgnoreCase('"ncu-test-v2": "~2.0.0"')
})
}

/** Assertions for npm or yarn when tests fail. */
const testFail = ({ packageManager }: { packageManager: PackageManagerName }) => {
it('identify broken upgrade', async function () {
const cwd = path.join(doctorTests, 'fail')
const pkgPath = path.join(cwd, 'package.json')
const nodeModulesPath = path.join(cwd, 'node_modules')
const lockfilePath = path.join(
cwd,
packageManager === 'yarn'
? 'yarn.lock'
: packageManager === 'pnpm'
? 'pnpm-lock.yaml'
: packageManager === 'bun'
? 'bun.lockb'
: 'package-lock.json',
)
const pkgOriginal = await fs.readFile(path.join(cwd, 'package.json'), 'utf-8')
let stdout = ''
let stderr = ''
let pkgUpgraded

// touch yarn.lock (see fail/README)
if (packageManager === 'yarn') {
await fs.writeFile(lockfilePath, '')
}

try {
// explicitly set packageManager to avoid auto yarn detection
await ncu(
['--doctor', '-u', '-p', packageManager],
{
stdout: function (data: string) {
stdout += data
},
stderr: function (data: string) {
stderr += data
},
},
{ cwd },
)
} finally {
pkgUpgraded = await fs.readFile(pkgPath, 'utf-8')
await fs.writeFile(pkgPath, pkgOriginal)
await fs.rm(nodeModulesPath, { recursive: true, force: true })
await fs.rm(lockfilePath, { recursive: true, force: true })

// delete yarn cache
if (packageManager === 'yarn') {
await fs.rm(path.join(cwd, '.yarn'), { recursive: true, force: true })
await fs.rm(path.join(cwd, '.pnp.js'), { recursive: true, force: true })
}
}

// stdout should include successful upgrades
stdout.should.containIgnoreCase('ncu-test-v2 ~1.0.0 →')
stdout.should.not.include('ncu-test-return-version ~1.0.0 →')
stdout.should.containIgnoreCase('emitter20 1.0.0 →')

// stderr should include first failing upgrade
stderr.should.containIgnoreCase('Breaks with v2.x')
stderr.should.not.include('ncu-test-v2 ~1.0.0 →')
stderr.should.containIgnoreCase('ncu-test-return-version ~1.0.0 →')
stderr.should.not.include('emitter20 1.0.0 →')

// package file should only include successful upgrades
pkgUpgraded.should.containIgnoreCase('"ncu-test-v2": "~2.0.0"')
pkgUpgraded.should.containIgnoreCase('"ncu-test-return-version": "~1.0.0"')
pkgUpgraded.should.not.include('"emitter20": "1.0.0"')
})
}

describe('doctor', function () {
// 3 min timeout
this.timeout(3 * 60 * 1000)
Expand Down Expand Up @@ -482,10 +338,4 @@ else {
testPass({ packageManager: 'yarn' })
testFail({ packageManager: 'yarn' })
})

// TODO: Works locally, but not in GitHub action.
describe.skip('bun', () => {
testPass({ packageManager: 'bun' })
testFail({ packageManager: 'bun' })
})
})
161 changes: 161 additions & 0 deletions test/helpers/doctorHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import fs from 'fs/promises'
import path from 'path'
import spawn from 'spawn-please'
import { PackageManagerName } from '../../src/types/PackageManagerName'

const bin = path.join(__dirname, '../../build/cli.js')
const doctorTests = path.join(__dirname, '../test-data/doctor')

/** Run the ncu CLI. */
const ncu = async (
args: string[],
spawnPleaseOptions?: Parameters<typeof spawn>[2],
spawnOptions?: Parameters<typeof spawn>[3],
) => {
const { stdout } = await spawn('node', [bin, ...args], spawnPleaseOptions, spawnOptions)
return stdout
}

/** Assertions for npm or yarn when tests pass. */
export const testPass = ({ packageManager }: { packageManager: PackageManagerName }) => {
it('upgrade dependencies when tests pass', async function () {
// use dynamic import for ESM module
const { default: stripAnsi } = await import('strip-ansi')
const cwd = path.join(doctorTests, 'pass')
const pkgPath = path.join(cwd, 'package.json')
const nodeModulesPath = path.join(cwd, 'node_modules')
const lockfilePath = path.join(
cwd,
packageManager === 'yarn'
? 'yarn.lock'
: packageManager === 'pnpm'
? 'pnpm-lock.yaml'
: packageManager === 'bun'
? 'bun.lockb'
: 'package-lock.json',
)
const pkgOriginal = await fs.readFile(path.join(cwd, 'package.json'), 'utf-8')
let stdout = ''
let stderr = ''

// touch yarn.lock
// yarn.lock is necessary otherwise yarn sees the package.json in the npm-check-updates directory and throws an error.
if (packageManager === 'yarn' || packageManager === 'bun') {
await fs.writeFile(lockfilePath, '')
}

try {
// explicitly set packageManager to avoid auto yarn detection
await ncu(
['--doctor', '-u', '-p', packageManager],
{
stdout: function (data: string) {
stdout += data
},
stderr: function (data: string) {
stderr += data
},
},
{ cwd },
)
} catch (e) {}

const pkgUpgraded = await fs.readFile(pkgPath, 'utf-8')

// cleanup before assertions in case they fail
await fs.writeFile(pkgPath, pkgOriginal)
await fs.rm(nodeModulesPath, { recursive: true, force: true })
await fs.rm(lockfilePath, { recursive: true, force: true })

// delete yarn cache
if (packageManager === 'yarn') {
await fs.rm(path.join(cwd, '.yarn'), { recursive: true, force: true })
await fs.rm(path.join(cwd, '.pnp.js'), { recursive: true, force: true })
}

// bun prints the run header to stderr instead of stdout
if (packageManager === 'bun') {
stripAnsi(stderr).should.equal('$ echo Success\n\n$ echo Success\n\n')
} else {
stderr.should.equal('')
}

// stdout should include normal output
stripAnsi(stdout).should.containIgnoreCase('Tests pass')
stripAnsi(stdout).should.containIgnoreCase('ncu-test-v2 ~1.0.0 → ~2.0.0')

// package file should include upgrades
pkgUpgraded.should.containIgnoreCase('"ncu-test-v2": "~2.0.0"')
})
}

/** Assertions for npm or yarn when tests fail. */
export const testFail = ({ packageManager }: { packageManager: PackageManagerName }) => {
it('identify broken upgrade', async function () {
const cwd = path.join(doctorTests, 'fail')
const pkgPath = path.join(cwd, 'package.json')
const nodeModulesPath = path.join(cwd, 'node_modules')
const lockfilePath = path.join(
cwd,
packageManager === 'yarn'
? 'yarn.lock'
: packageManager === 'pnpm'
? 'pnpm-lock.yaml'
: packageManager === 'bun'
? 'bun.lockb'
: 'package-lock.json',
)
const pkgOriginal = await fs.readFile(path.join(cwd, 'package.json'), 'utf-8')
let stdout = ''
let stderr = ''
let pkgUpgraded

// touch yarn.lock (see fail/README)
if (packageManager === 'yarn') {
await fs.writeFile(lockfilePath, '')
}

try {
// explicitly set packageManager to avoid auto yarn detection
await ncu(
['--doctor', '-u', '-p', packageManager],
{
stdout: function (data: string) {
stdout += data
},
stderr: function (data: string) {
stderr += data
},
},
{ cwd },
)
} finally {
pkgUpgraded = await fs.readFile(pkgPath, 'utf-8')
await fs.writeFile(pkgPath, pkgOriginal)
await fs.rm(nodeModulesPath, { recursive: true, force: true })
await fs.rm(lockfilePath, { recursive: true, force: true })

// delete yarn cache
if (packageManager === 'yarn') {
await fs.rm(path.join(cwd, '.yarn'), { recursive: true, force: true })
await fs.rm(path.join(cwd, '.pnp.js'), { recursive: true, force: true })
}
}

// stdout should include successful upgrades
stdout.should.containIgnoreCase('ncu-test-v2 ~1.0.0 →')
stdout.should.not.include('ncu-test-return-version ~1.0.0 →')
stdout.should.containIgnoreCase('emitter20 1.0.0 →')

// stderr should include first failing upgrade
stderr.should.containIgnoreCase('Breaks with v2.x')
stderr.should.not.include('ncu-test-v2 ~1.0.0 →')
stderr.should.containIgnoreCase('ncu-test-return-version ~1.0.0 →')
stderr.should.not.include('emitter20 1.0.0 →')

// package file should only include successful upgrades
pkgUpgraded.should.containIgnoreCase('"ncu-test-v2": "~2.0.0"')
pkgUpgraded.should.containIgnoreCase('"ncu-test-return-version": "~1.0.0"')
pkgUpgraded.should.not.include('"emitter20": "1.0.0"')
})
}