From 6a6856ddc0e59c918cf2707e42da0a06ce8cc196 Mon Sep 17 00:00:00 2001 From: Whitney Young Date: Sun, 15 Jan 2017 16:41:16 -0800 Subject: [PATCH] Simplify code base by using deasync --- .eslintignore | 2 +- package.json | 5 +- src/plugin.js | 94 ++++++------ src/postcss-client.js | 57 -------- src/postcss-server.js | 108 -------------- test/bootstrap.js | 2 - test/fixtures-no-config/require.js | 1 + test/fixtures/missingcss.js | 1 + test/fixtures/nocss.expected.js | 3 + test/helpers.js | 14 +- test/plugin.js | 152 +++++++------------- test/postcss-client.js | 117 --------------- test/postcss-server.js | 221 ----------------------------- 13 files changed, 119 insertions(+), 658 deletions(-) delete mode 100644 src/postcss-client.js delete mode 100644 src/postcss-server.js create mode 100644 test/fixtures-no-config/require.js create mode 100644 test/fixtures/missingcss.js create mode 100644 test/fixtures/nocss.expected.js delete mode 100644 test/postcss-client.js delete mode 100644 test/postcss-server.js diff --git a/.eslintignore b/.eslintignore index 1aef24a..732db9c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,4 @@ /dist/ /coverage/ /.nyc_output/ -/test/fixtures/ +/test/fixtures*/ diff --git a/package.json b/package.json index cc0f14f..01b8f55 100644 --- a/package.json +++ b/package.json @@ -63,14 +63,13 @@ "mocha": "^3.2.0", "nyc": "^10.0.0", "postcss": "^5.2.9", - "postcss-modules": "^0.6.2", - "sinon": "^1.17.7", - "sinon-chai": "^2.8.0" + "postcss-modules": "^0.6.2" }, "peerDependencies": { "postcss": "^5.2.9" }, "dependencies": { + "deasync": "^0.1.9", "debug": "^2.6.0", "postcss-load-config": "^1.1.0" } diff --git a/src/plugin.js b/src/plugin.js index c2e094d..3791b50 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -4,51 +4,40 @@ import { dirname, extname, resolve, - join, } from 'path'; -import { - execFileSync, - spawn, -} from 'child_process'; - -// note: socket path is important to keep short as it will be truncated if it -// exceeds certain platform limits. for this reason, we're writing to /tmp -// instead of using os.tmpdir (which can, on platforms like darwin, be quite -// long & per-process). -const projectId = process.cwd().toLowerCase().replace(/[^a-z]/g, ''); -const socketName = `bptp-${projectId}.sock`; -const socketPath = join('/tmp', socketName); - -const nodeExecutable = process.argv[0]; -const clientExcutable = join(__dirname, 'postcss-client.js'); -const serverExcutable = join(__dirname, 'postcss-server.js'); - -let server; - -const startServer = () => { - server = spawn(nodeExecutable, [serverExcutable, socketPath], { - env: process.env, // eslint-disable-line no-process-env - stdio: 'inherit', - }); - - server.unref(); -}; +import fs from 'fs'; +import util from 'util'; +import postcss from 'postcss'; +import loadConfig from 'postcss-load-config'; +import deasync from 'deasync'; + +const sync = (promise: Promise): T => { + let success: { result: T }, error: Error; + + promise.then( + (result: T) => { success = { result }; }, + (err: Error) => { error = err; }, + ); + deasync.loopWhile(() => !(success || error)); -const stopServer = () => { - if (!server) { return; } + if (!success) { + throw error; + } - server.kill(); - server = null; - process.removeListener('exit', stopServer); + return success.result; }; -const launchServer = () => { - if (server) { return; } +const streams = { stderr: process.stderr }; // overwritable by tests +const error = (...args: any) => { + let prefix = 'babel-plugin-transform-postcss: '; + const message = util.format(...args); - startServer(); + if (streams.stderr.isTTY) { + prefix = `\x1b[31m${prefix}\x1b[0m`; + } - process.on('exit', stopServer); + streams.stderr.write(`${prefix}${message}\n`); }; export default function transformPostCSS({ types: t }: any): any { @@ -69,16 +58,30 @@ export default function transformPostCSS({ types: t }: any): any { const stylesheetExtension = extname(stylesheetPath); if (extensions.indexOf(stylesheetExtension) !== -1) { - launchServer(); + let config, source; + let tokens = {}; const requiringFile = file.opts.filename; const cssFile = resolve(dirname(requiringFile), stylesheetPath); - const data = JSON.stringify({ cssFile }); - const execArgs = [clientExcutable, socketPath, data]; - const result = execFileSync(nodeExecutable, execArgs, { - env: process.env, // eslint-disable-line no-process-env - }).toString('utf8'); - const tokens = JSON.parse(result || '{}'); + const extractModules = (_, resultTokens: any) => { + tokens = resultTokens; + }; + + try { + config = sync(loadConfig({ extractModules }, dirname(cssFile))); + source = // eslint-disable-next-line no-sync + fs.readFileSync(cssFile, 'utf8'); + } + catch (err) { + error(err.stack); + + return; + } + + const { plugins, postcssOpts } = config; + const runner = postcss(plugins); + + sync(runner.process(source, postcssOpts)); const expression = path.findParent((test) => ( test.isVariableDeclaration() || @@ -104,6 +107,5 @@ export default function transformPostCSS({ types: t }: any): any { } export { - startServer, - stopServer, + streams as _streams, }; diff --git a/src/postcss-client.js b/src/postcss-client.js deleted file mode 100644 index 9e5af49..0000000 --- a/src/postcss-client.js +++ /dev/null @@ -1,57 +0,0 @@ -/* @flow */ - -import net from 'net'; - -// exponential backoff, roughly 100ms-6s -const retries = [1, 2, 3, 4, 5].map((num) => Math.exp(num) * 40); -const streams = { stdout: process.stdout }; // overwritable by tests - -const communicate = async function communicate( - socketPath: string, - message: string, -): Promise { - await new Promise((resolve: () => void, reject: (Error) => void) => { - const client = net.connect(socketPath, () => { - client.end(message); - client.pipe(streams.stdout); - }); - - client.on('error', (err: ErrnoError) => reject(err)); - client.on('close', (err: ?Error) => { - if (err) { reject(err); } - else { resolve(); } - }); - }); -}; - -const main = async function main(...args: string[]): Promise { - try { await communicate(...args); } - catch (err) { - const recoverable = ( - err.code === 'ECONNREFUSED' || - err.code === 'ENOENT' - ); - - if (recoverable && retries.length) { - await new Promise((resolve: () => void, reject: (Error) => void) => { - setTimeout(() => { - main(...args).then(resolve, reject); - }, retries.shift()); - }); - } - } -}; - -/* istanbul ignore if */ -if ((require: any).main === module) { - (async (): Promise => { - try { await main(...process.argv.slice(2)); } - catch (err) { process.stderr.write(`${err.stack}\n`); process.exit(1); } - })(); -} - -export { - main, - streams as _streams, - retries as _retries, -}; diff --git a/src/postcss-server.js b/src/postcss-server.js deleted file mode 100644 index 2755218..0000000 --- a/src/postcss-server.js +++ /dev/null @@ -1,108 +0,0 @@ -/* @flow */ - -import net from 'net'; -import fs from 'fs'; -import path from 'path'; -import util from 'util'; -import makeDebug from 'debug'; -import postcss from 'postcss'; -import loadConfig from 'postcss-load-config'; -import type { Socket, Server } from 'net'; - -const debug = makeDebug('babel-plugin-transform-postcss'); -const streams = { stderr: process.stderr }; // overwritable by tests -const error = (...args: any) => { - let prefix = 'babel-plugin-transform-postcss: '; - const message = util.format(...args); - - if (streams.stderr.isTTY) { - prefix = `\x1b[31m${prefix}\x1b[0m`; - } - - streams.stderr.write(`${prefix}${message}\n`); -}; - -const main = async function main(socketPath: string): Promise { - const options = { allowHalfOpen: true }; - const server = net.createServer(options, (connection: Socket) => { - let data: string = ''; - - connection.on('data', (chunk: Buffer) => { - data += chunk.toString('utf8'); - }); - - connection.on('end', async (): Promise => { - try { - let tokens; - const { cssFile } = JSON.parse(data); - - const extractModules = (_, resultTokens: any) => { - tokens = resultTokens; - }; - const { plugins, postcssOpts } = - await loadConfig({ extractModules }, path.dirname(cssFile)); - - // eslint-disable-next-line no-sync - const source = fs.readFileSync(cssFile, 'utf8'); - const runner = postcss(plugins); - - await runner.process(source, postcssOpts); - - connection.end(JSON.stringify(tokens)); - } - catch (err) { - error(err.stack); - connection.end(); - } - }); - }); - - if (fs.existsSync(socketPath)) { // eslint-disable-line no-sync - error(`Server already running on socket ${socketPath}`); - process.exit(1); - - return server; // tests can make it past process.exit - } - - await new Promise((resolve: () => void, reject: (Error) => void) => { - server.on('error', (err: Error) => reject(err)); - server.on('listening', () => { - const handler = () => { - fs.unlinkSync(socketPath); // eslint-disable-line no-sync - }; - - server.on('close', () => { - process.removeListener('exit', handler); - process.removeListener('SIGINT', handler); - process.removeListener('SIGTERM', handler); - }); - - process.on('exit', handler); - process.on('SIGINT', handler); - process.on('SIGTERM', handler); - - resolve(); - }); - - server.listen(socketPath, () => { - debug( - `babel-plugin-transform-postcss server running on socket ${socketPath}` - ); - }); - }); - - return server; -}; - -/* istanbul ignore if */ -if ((require: any).main === module) { - (async (): Promise => { - try { await main(...process.argv.slice(2)); } - catch (err) { process.stderr.write(`${err.stack}\n`); process.exit(1); } - })(); -} - -export { - main, - streams as _streams, -}; diff --git a/test/bootstrap.js b/test/bootstrap.js index 1796b6a..fb8d7dc 100644 --- a/test/bootstrap.js +++ b/test/bootstrap.js @@ -2,7 +2,5 @@ import chai from 'chai'; import chaiString from 'chai-string'; -import sinonChai from 'sinon-chai'; chai.use(chaiString); -chai.use(sinonChai); diff --git a/test/fixtures-no-config/require.js b/test/fixtures-no-config/require.js new file mode 100644 index 0000000..bc7c132 --- /dev/null +++ b/test/fixtures-no-config/require.js @@ -0,0 +1 @@ +var styles = require('./simple.css'); diff --git a/test/fixtures/missingcss.js b/test/fixtures/missingcss.js new file mode 100644 index 0000000..23d21c1 --- /dev/null +++ b/test/fixtures/missingcss.js @@ -0,0 +1 @@ +var styles = require('./nofile.css'); diff --git a/test/fixtures/nocss.expected.js b/test/fixtures/nocss.expected.js new file mode 100644 index 0000000..effe0dc --- /dev/null +++ b/test/fixtures/nocss.expected.js @@ -0,0 +1,3 @@ +'use strict'; + +var styles = require('./simple'); diff --git a/test/helpers.js b/test/helpers.js index adf257d..90bf39e 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -4,9 +4,11 @@ import fs from 'fs'; import path from 'path'; import * as babel from 'babel-core'; -const fixtures = path.join(__dirname, 'fixtures'); - -export const transform = (filename: string): Promise => { +export const transform = ( + filename: string, + dirname: string='fixtures', +): Promise => { + const fixtures = path.join(__dirname, dirname); const file = path.join(fixtures, filename); const options = { presets: [ ['env', { targets: { node: 'current' } }] ], @@ -25,7 +27,11 @@ export const transform = (filename: string): Promise => { }); }; -export const read = (filename: string): Promise => { +export const read = ( + filename: string, + dirname: string='fixtures', +): Promise => { + const fixtures = path.join(__dirname, dirname); const file = path.join(fixtures, filename); const options = { encoding: 'utf8', diff --git a/test/plugin.js b/test/plugin.js index cc0b03f..9b2660c 100644 --- a/test/plugin.js +++ b/test/plugin.js @@ -14,134 +14,88 @@ import { } from './helpers'; import { - startServer, - stopServer, + _streams as streams, } from '../src/plugin'; +import path from 'path'; +import fs from 'fs'; + import { expect } from 'chai'; -import { stub } from 'sinon'; -import childProcess from 'child_process'; describe('babel-plugin-transform-postcss', () => { - beforeEach(() => { - stub(childProcess, 'spawn').returns({ - unref: stub(), - kill: stub(), - }); + it('compiles require.js correctly', async () => { + expect(await transform('require.js')) + .to.eql((await read('require.expected.js')).trim()); }); - afterEach(() => childProcess.spawn.restore()); - beforeEach(() => { - stub(childProcess, 'execFileSync').returns( - new Buffer('{"simple":"_simple_jvai8_1"}') - ); + it('compiles import.js correctly', async () => { + expect(await transform('import.js')) + .to.eql((await read('import.expected.js')).trim()); }); - afterEach(() => childProcess.execFileSync.restore()); - - afterEach(() => stopServer()); - const testServerLaunched = () => { - expect(childProcess.spawn).to.have.been.calledOnce; + it('compiles nocss.js correctly', async () => { + expect(await transform('nocss.js')) + .to.eql((await read('nocss.expected.js')).trim()); + }); - const [executable, args, opts] = childProcess.spawn.getCall(0).args; - const [jsExecutable, socketPath] = args; + const testOutput = path.join(__dirname, 'tmp.out'); + const setupStreamCapture = () => { + let originalStderr; - expect(executable).to.match(/node$/); - expect(args.length).to.eql(2); - expect(jsExecutable).to.endWith('/postcss-server.js'); - expect(socketPath).to.match(/^\/tmp.*\.sock$/); - expect(opts).to.eql({ - env: process.env, // eslint-disable-line no-process-env - stdio: 'inherit', + beforeEach(() => { + originalStderr = streams.stderr; + streams.stderr = fs.createWriteStream(testOutput); }); - }; - - const testClientLaunched = (filename: string) => { - expect(childProcess.execFileSync).to.have.been.calledOnce; - - const [executable, args, opts] = childProcess.execFileSync.getCall(0).args; - const [jsExecutable, socketPath, jsonString] = args; - const json = JSON.parse(jsonString); - const { cssFile } = json; - - expect(executable).to.match(/node$/); - expect(args.length).to.eql(3); - expect(jsExecutable).to.endWith('/postcss-client.js'); - expect(socketPath).to.match(/^\/tmp.*\.sock$/); - expect(cssFile).to.endWith(`/${filename}`); - expect(json).to.have.keys('cssFile'); - expect(opts).to.eql({ - env: process.env, // eslint-disable-line no-process-env + afterEach(() => { + streams.stderr = originalStderr; + fs.unlinkSync(testOutput); }); }; - const shouldBehaveLikeSeverIsRunning = () => { - describe('when transforming require.js', () => { - let initialSpwawnCount; - - beforeEach(() => { initialSpwawnCount = childProcess.spawn.callCount; }); - beforeEach(() => transform('require.js')); - - it('does not launch the server again', () => { - expect(childProcess.spawn.callCount).to.eql(initialSpwawnCount); - }); + const finishStreamCapture = async () => { + const write = new Promise((resolve: () => void) => { + streams.stderr.on('finish', () => resolve()); }); - }; - - describe('when transforming require.js', () => { - let result; - - beforeEach(async () => { result = await transform('require.js'); }); - it('launches the server', testServerLaunched); - it('launches a client', () => testClientLaunched('simple.css')); - it('compiles correctly', async () => { - expect(result).to.eql((await read('require.expected.js')).trim()); - }); - - shouldBehaveLikeSeverIsRunning(); - }); - - describe('when transforming import.js', () => { - let result; + streams.stderr.end(); + await write; + }; - beforeEach(async () => { result = await transform('import.js'); }); + describe('with a missing CSS file', () => { + setupStreamCapture(); + beforeEach(() => transform('missingcss.js')); + beforeEach(finishStreamCapture); - it('launches the server', testServerLaunched); - it('launches a client', () => testClientLaunched('simple.css')); - it('compiles correctly', async () => { - expect(result).to.eql((await read('import.expected.js')).trim()); + it('logs a useful message', () => { + expect(fs.readFileSync(testOutput, 'utf8')) + .to.match(/no such file/i); }); - - shouldBehaveLikeSeverIsRunning(); }); - describe('when transforming nocss.js', () => { - beforeEach(() => transform('nocss.js')); - it('does not launch the server', () => { - expect(childProcess.spawn).to.not.have.been.called; - }); + describe('with a missing config file', () => { + setupStreamCapture(); + beforeEach(() => transform('require.js', 'fixtures-no-config')); + beforeEach(finishStreamCapture); - it('does not launch a client', () => { - expect(childProcess.execFileSync).to.not.have.been.called; + it('logs a useful message', () => { + expect(fs.readFileSync(testOutput, 'utf8')) + .to.match(/No PostCSS Config/i); }); }); - describe('when the server has been started started', () => { - beforeEach(() => startServer()); - shouldBehaveLikeSeverIsRunning(); - }); - - describe('when transforming require.js & the client returns no data', () => { - let result; + describe('when stderr is a TTY', () => { + setupStreamCapture(); + beforeEach(() => { (streams.stderr: any).isTTY = true; }); - beforeEach(() => childProcess.execFileSync.returns(new Buffer(''))); - beforeEach(async () => { result = await transform('require.js'); }); + describe('with a missing config file', () => { + beforeEach(() => transform('require.js', 'fixtures-no-config')); + beforeEach(finishStreamCapture); - it('compiles correctly', async () => { - expect(result).to.eql((await read('require.expected.empty.js')).trim()); + it('logs with color', () => { + expect(fs.readFileSync(testOutput, 'utf8')) + .to.startWith('\x1b[31m'); + }); }); }); - }); diff --git a/test/postcss-client.js b/test/postcss-client.js deleted file mode 100644 index fdaa32a..0000000 --- a/test/postcss-client.js +++ /dev/null @@ -1,117 +0,0 @@ -/* @flow */ -/* eslint-disable no-sync */ - -import { - describe, - it, - beforeEach, - afterEach, -} from 'mocha'; - -import { - main, - _streams as streams, - _retries as retries, -} from '../src/postcss-client'; - -import { - spy, -} from 'sinon'; - -import { expect } from 'chai'; - -import { join } from 'path'; -import fs from 'fs'; -import net from 'net'; -import type { Socket } from 'net'; - -const testSocket = join(__dirname, 'tmp.sock'); -const testOutput = join(__dirname, 'tmp.out'); - -describe('postcss-client', () => { - let originalStdout, originalRetries; - - beforeEach(() => { - originalStdout = streams.stdout; - streams.stdout = fs.createWriteStream(testOutput); - }); - afterEach(() => { - streams.stdout = originalStdout; - fs.unlinkSync(testOutput); - }); - - beforeEach(() => { - originalRetries = [...retries]; - retries.splice(0, retries.length, 1); - }); - afterEach(() => { - retries.splice(0, retries.length, ...originalRetries); - }); - - beforeEach(() => spy(net, 'connect')); - afterEach(() => net.connect.restore()); - - describe('with a server to connect to', () => { - let server, received; - - beforeEach(() => { - received = ''; - server = net.createServer({ allowHalfOpen: true }, (conn: Socket) => { - conn.on('data', (chunk: Buffer) => { - received += chunk.toString('utf8'); - }); - conn.on('end', () => { - conn.end('server output'); - }); - }); - - return new Promise((resolve: () => void, reject: (Error) => void) => { - server.listen(testSocket, (err: ?Error) => { - if (err) { reject(err); } - else { resolve(); } - }); - }); - }); - - afterEach(async () => { - await new Promise((resolve: () => void, reject: (Error) => void) => { - fs.unlinkSync(testSocket); - server.close((err: ?Error) => { - if (err) { reject(err); } - else { resolve(); } - }); - }); - }); - - describe('main(...testArgs)', () => { - beforeEach(async () => { - const write = new Promise((resolve: () => void) => { - streams.stdout.on('finish', () => resolve()); - }); - - await Promise.all([main(testSocket, 'client message'), write]); - }); - - it('sends client message to server', () => { - expect(received).to.eql('client message'); - }); - - it('writes server response to stdout', () => { - expect(fs.readFileSync(testOutput, 'utf8')).to.eql('server output'); - }); - - it('succeeds during first connect attempt', () => { - expect(net.connect).to.have.been.calledOnce; - }); - }); - }); - - describe('main(...testArgs)', () => { - beforeEach(async () => { await main(testSocket, 'client message'); }); - - it('attempts to re-connect', () => { - expect(net.connect).to.have.been.calledTwice; - }); - }); - -}); diff --git a/test/postcss-server.js b/test/postcss-server.js deleted file mode 100644 index 611ef11..0000000 --- a/test/postcss-server.js +++ /dev/null @@ -1,221 +0,0 @@ -/* @flow */ -/* eslint-disable no-sync */ - -import { - describe, - it, - beforeEach, - afterEach, -} from 'mocha'; - -import { - main, - _streams as streams, -} from '../src/postcss-server'; - -import { - stub, -} from 'sinon'; - -import { expect } from 'chai'; - -import { join } from 'path'; -import path from 'path'; -import fs from 'fs'; -import net from 'net'; -import { Server } from 'net'; - -const testSocket = join(__dirname, 'tmp.sock'); -const testOutput = join(__dirname, 'tmp.out'); - -describe('postcss-server', () => { - let server, originalStderr; - const invokeMain = async () => { server = await main(testSocket); }; - const closeServer = async() => { - await new Promise((resolve: () => void, reject: (Error) => void) => { - server.close((err: ?Error) => { - if (err) { reject(err); } - else { resolve(); } - }); - }); - }; - const closeStderr = async () => { - const write = new Promise((resolve: () => void) => { - streams.stderr.on('finish', () => resolve()); - }); - - streams.stderr.end(); - await write; - }; - - beforeEach(() => { - originalStderr = streams.stderr; - streams.stderr = fs.createWriteStream(testOutput); - }); - afterEach(() => { - streams.stderr = originalStderr; - fs.unlinkSync(testOutput); - }); - - describe('main(...testArgs)', () => { - let signintHandlers; - - beforeEach(() => { signintHandlers = process.listeners('SIGINT'); }); - - beforeEach(invokeMain); - afterEach(closeServer); - - it('starts a server', () => { - expect(server.address()).to.eql(testSocket); - }); - - it('observes SIGINT to cleanup server socket', () => { - const newHandlers = process.listeners('SIGINT') - .slice(signintHandlers.length); - - expect(newHandlers.length).to.eql(1); - newHandlers[0](); - - expect(fs.existsSync(testSocket)).to.be.false; - }); - - const sendMessage = async ( - json: { - cssFile: string, - } - ): Promise => { - let response = ''; - - await new Promise((resolve: () => void, reject: (Error) => void) => { - const client = net.connect(testSocket, () => { - client.end(JSON.stringify(json)); - client.on('data', (chunk: Buffer) => { - response += chunk.toString('utf8'); - }); - }); - - client.on('error', (err: ErrnoError) => reject(err)); - client.on('close', (err: ?Error) => { - if (err) { reject(err); } - else { resolve(); } - }); - }); - - return response; - }; - - it('accepts JSON details and extracts PostCSS modules', async () => { - const response = await sendMessage({ - cssFile: join(__dirname, 'fixtures', 'simple.css'), - }); - - expect(JSON.parse(response)).to.eql({ simple: '_simple_jvai8_1' }); - }); - - it('fails gracefully for invalid CSS', async () => { - const response = await sendMessage({ - cssFile: join(__dirname, 'fixtures', 'invalid.css'), - }); - - expect(response).to.eql(''); - }); - - describe('with a missing CSS file', () => { - let response; - - beforeEach(async () => { - response = await sendMessage({ - cssFile: join(__dirname, 'fixtures', 'nofile'), - }); - }); - beforeEach(closeStderr); - - it('does not contain a response', () => { - expect(response).to.eql(''); - }); - - it('logs a useful message', () => { - expect(fs.readFileSync(testOutput, 'utf8')) - .to.match(/no such file/i); - }); - }); - - describe('with a missing config file', () => { - let response; - - beforeEach(() => stub(path, 'dirname', () => process.cwd())); - afterEach(() => path.dirname.restore()); - - beforeEach(async () => { - response = await sendMessage({ - cssFile: join(__dirname, 'fixtures', 'simple.css'), - configFile: join(__dirname, 'fixtures', 'nofile'), - }); - }); - beforeEach(closeStderr); - - it('does not contain a response', () => { - expect(response).to.eql(''); - }); - - it('logs a useful message', () => { - expect(fs.readFileSync(testOutput, 'utf8')) - .to.match(/No PostCSS Config/i); - }); - }); - }); - - describe('when listen fails', () => { - beforeEach(() => { - stub(Server.prototype, 'listen', function errorHandler() { - this.emit('error', new Error('test failure')); - }); - }); - afterEach(() => { Server.prototype.listen.restore(); }); - - it('fails to complete main(...testArgs)', async () => { - let error; - - try { await invokeMain(); } - catch (err) { error = err; } - expect(error).to.match(/test failure/); - }); - }); - - describe('when the server socket already exists', () => { - beforeEach(() => { stub(process, 'exit'); }); - afterEach(() => { process.exit.restore(); }); - - beforeEach(() => { fs.writeFileSync(testSocket, ''); }); - afterEach(() => { fs.unlinkSync(testSocket); }); - - describe('main(...testArgs)', () => { - beforeEach(invokeMain); - beforeEach(closeStderr); - - it('exits', () => { - expect(process.exit).to.have.been.calledOnce; - expect(process.exit).to.have.been.calledWith(1); - }); - - it('logs a useful message', () => { - expect(fs.readFileSync(testOutput, 'utf8')) - .to.match(/already running/i); - }); - }); - - describe('when stderr is a TTY', () => { - beforeEach(() => { (streams.stderr: any).isTTY = true; }); - - describe('main(...testArgs)', () => { - beforeEach(invokeMain); - beforeEach(closeStderr); - - it('logs with color', () => { - expect(fs.readFileSync(testOutput, 'utf8')).to.startWith('\x1b[31m'); - }); - }); - }); - - }); -});