diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2c54912..07e7f60 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -132,7 +132,7 @@ jobs: run: ls -R . shell: bash - name: Test bindings - run: bun test:darwin + run: npm run test:darwin test-linux-binding: name: Test on ${{ matrix.settings.target }} diff --git a/bun.lockb b/bun.lockb index 60ed448..6d8021b 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 94db195..1cc36de 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,9 @@ "license": "MIT", "devDependencies": { "@napi-rs/cli": "^2.18.2", - "@types/node": "^20.4.1", "@types/jest": "^29.5.11", + "@types/node": "^20.4.1", + "jest": "^29.7.0", "prettier": "^3.2.4" }, "scripts": { @@ -35,11 +36,11 @@ "build:debug": "napi build --platform", "prepublishOnly": "napi prepublish -t npm", "test:linux": "bun test tests/linux.test.ts", - "test:darwin": "bun test tests/darwin.test.ts", + "test:darwin": "npx jest tests/darwin.test.js", "universal": "napi universal", "version": "napi version", "release": "npm publish --access public", - "format": "npx prettier *.ts --write" + "format": "npx prettier *.ts,*.js --write" }, "optionalDependencies": { "@replit/ruspty-darwin-x64": "1.0.0-alpha.1", diff --git a/tests/darwin.test.js b/tests/darwin.test.js new file mode 100644 index 0000000..b02e880 --- /dev/null +++ b/tests/darwin.test.js @@ -0,0 +1,214 @@ +const { Pty } = require('../index'); +const fs = require('fs'); + +const EOT = '\x04'; + +describe('PTY', () => { + test('spawns and exits', (done) => { + const message = 'hello from a pty'; + let buffer = ''; + + const pty = new Pty({ + command: '/bin/echo', + args: [message], + onExit: (err, exitCode) => { + expect(err).toBeNull(); + expect(exitCode).toBe(0); + expect(buffer).toBe(message + '\r\n'); + pty.close(); + + done(); + }, + }); + + setImmediate(() => { + const fd = pty.fd(); + const readStream = fs.createReadStream('', { fd }); + + readStream.on('data', (chunk) => { + buffer = chunk.toString(); + }); + }); + }); + + test('captures an exit code', (done) => { + const pty = new Pty({ + command: '/bin/sh', + args: ['-c', 'exit 17'], + onExit: (err, exitCode) => { + expect(err).toBeNull(); + expect(exitCode).toBe(17); + pty.close(); + + done(); + }, + }); + }); + + test('can be written to', (done) => { + // The message should end in newline so that the EOT can signal that the input has ended and not + // just the line. + const message = 'hello cat\n'; + let buffer = ''; + + // We have local echo enabled, so we'll read the message twice. + // `cat` on darwin also logs `^D` + const result = 'hello cat\r\n^D\b\bhello cat\r\n'; + + const pty = new Pty({ + command: '/bin/cat', + onExit: () => { + expect(buffer).toBe(result); + + pty.close(); + + done(); + }, + }); + + setImmediate(() => { + const writeFd = pty.fd(); + const writeStream = fs.createWriteStream('', { fd: writeFd }); + + const readFd = pty.fd(); + const readStream = fs.createReadStream('', { fd: readFd }); + + readStream.on('data', (data) => { + buffer += data.toString(); + }); + + writeStream.write(message); + writeStream.end(EOT); + writeStream.on('error', (err) => { + if (err.code && err.code.indexOf('EIO') !== -1) { + return; + } + throw err; + }); + }); + }); + + // TODO: fix this test on macOS + test.skip('can be resized', (done) => { + let buffer = ''; + + const pty = new Pty({ + command: '/bin/sh', + size: { rows: 24, cols: 80 }, + onExit: () => { + pty.close(); + + done(); + }, + }); + + setImmediate(() => { + const writeFd = pty.fd(); + const writeStream = fs.createWriteStream('', { fd: writeFd }); + + const readFd = pty.fd(); + const readStream = fs.createReadStream('', { fd: readFd }); + + readStream.on('data', (data) => { + buffer += data.toString(); + + console.log('buffer'); + console.log(buffer); + console.log('-----'); + + if (buffer.includes('done1\r\n')) { + expect(buffer).toContain('24 80'); + pty.resize({ rows: 60, cols: 100 }); + buffer = ''; + + writeStream.write("stty size; echo 'done2'\n"); + } + + if (buffer.includes('done2\r\n')) { + expect(buffer).toContain('60 100'); + } + }); + + writeStream.write("stty size; echo 'done1'\n"); + writeStream.on('error', (err) => { + if (err.code && err.code.indexOf('EIO') !== -1) { + return; + } + throw err; + }); + }); + }); + + // TODO: fix this test on macOS + test.skip('respects working directory', (done) => { + const cwd = process.cwd(); + let buffer = ''; + + const pty = new Pty({ + command: '/bin/pwd', + dir: cwd, + onExit: (err, exitCode) => { + expect(err).toBeNull(); + expect(exitCode).toBe(0); + expect(buffer).toBe(`${cwd}\r\n`); + pty.close(); + + done(); + }, + }); + + setImmediate(() => { + const readFd = pty.fd(); + const readStream = fs.createReadStream('', { fd: readFd }); + + readStream.on('data', (data) => { + buffer += data.toString(); + }); + }); + }); + + // TODO: fix this test on macOS + test.skip('respects env', (done) => { + const message = 'hello from env'; + let buffer; + + const pty = new Pty({ + command: '/bin/sh', + args: ['-c', 'echo $ENV_VARIABLE && exit'], + envs: { + ENV_VARIABLE: message, + }, + onExit: (err, exitCode) => { + expect(err).toBeNull(); + expect(exitCode).toBe(0); + assert(buffer); + expect(Buffer.compare(buffer, Buffer.from(message + '\r\n'))).toBe(0); + pty.close(); + + done(); + }, + }); + + setImmediate(() => { + const readFd = pty.fd(); + const readStream = fs.createReadStream('', { fd: readFd }); + + readStream.on('data', (data) => { + buffer += data.toString(); + }); + }); + }); + + test("doesn't break when executing non-existing binary", (done) => { + try { + new Pty({ + command: '/bin/this-does-not-exist', + onExit: () => {}, + }); + } catch (e) { + expect(e.message).toContain('No such file or directory'); + + done(); + } + }); +}); diff --git a/tests/darwin.test.ts b/tests/darwin.test.ts deleted file mode 100644 index 4c17d5c..0000000 --- a/tests/darwin.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -// TODO: someday, test macOS -- best on node.js, or Bun if the `fd` issues are fixeds - -describe('PTY', () => {}); diff --git a/tests/linux.test.ts b/tests/linux.test.ts index 0707373..b74798c 100644 --- a/tests/linux.test.ts +++ b/tests/linux.test.ts @@ -82,12 +82,12 @@ describe('PTY', () => { const message = 'hello cat\n'; let buffer = ''; + // We have local echo enabled, so we'll read the message twice. const result = 'hello cat\r\nhello cat\r\n'; const pty = new Pty({ command: 'cat', onExit: () => { - // We have local echo enabled, so we'll read the message twice. expect(buffer).toBe(result); pty.close();