diff --git a/lib/util/readShebang.js b/lib/util/readShebang.js index 5e83733..5e76908 100644 --- a/lib/util/readShebang.js +++ b/lib/util/readShebang.js @@ -13,8 +13,13 @@ function readShebang(command) { try { fd = fs.openSync(command, 'r'); fs.readSync(fd, buffer, 0, size, 0); - fs.closeSync(fd); - } catch (e) { /* Empty */ } + } catch (e) { /* Empty */ } finally { + if (fd !== undefined) { + try { + fs.closeSync(fd); + } catch (e) { /* Empty */ } + } + } // Attempt to extract shebang (null is returned if not a shebang) return shebangCommand(buffer.toString()); diff --git a/test/readShebang.test.js b/test/readShebang.test.js new file mode 100644 index 0000000..353f6a4 --- /dev/null +++ b/test/readShebang.test.js @@ -0,0 +1,51 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const readShebang = require('../lib/util/readShebang'); + +describe('readShebang', () => { + afterEach(() => jest.restoreAllMocks()); + + it('closes an opened descriptor when reading fails, including descriptor zero', () => { + jest.spyOn(fs, 'openSync').mockReturnValue(0); + jest.spyOn(fs, 'readSync').mockImplementation(() => { + throw new Error('read failed'); + }); + const close = jest.spyOn(fs, 'closeSync').mockImplementation(() => {}); + + expect(readShebang('command')).toBeNull(); + expect(close).toHaveBeenCalledTimes(1); + expect(close).toHaveBeenCalledWith(0); + }); + + it('does not close a descriptor when opening fails', () => { + jest.spyOn(fs, 'openSync').mockImplementation(() => { + throw new Error('open failed'); + }); + const close = jest.spyOn(fs, 'closeSync').mockImplementation(() => {}); + + expect(readShebang('command')).toBeNull(); + expect(close).not.toHaveBeenCalled(); + }); + + it('preserves the fallback when closing also fails', () => { + jest.spyOn(fs, 'openSync').mockReturnValue(0); + jest.spyOn(fs, 'readSync').mockImplementation(() => { + throw new Error('read failed'); + }); + jest.spyOn(fs, 'closeSync').mockImplementation(() => { + throw new Error('close failed'); + }); + + expect(readShebang('command')).toBeNull(); + }); + + it('still reads a valid shebang', () => { + const close = jest.spyOn(fs, 'closeSync'); + const command = path.join(__dirname, 'fixtures/shebang'); + + expect(readShebang(command)).toBe('node'); + expect(close).toHaveBeenCalledTimes(1); + }); +});