diff --git a/test/common.js b/test/common.js new file mode 100644 index 000000000..067c3022d --- /dev/null +++ b/test/common.js @@ -0,0 +1,19 @@ +'use strict'; + +const ChildProcess = require('child_process'); + +const internals = {}; + +internals.hasLsof = () => { + + try { + ChildProcess.execSync(`lsof -p ${process.pid}`, { stdio: 'ignore' }); + } + catch (err) { + return false; + } + + return true; +}; + +exports.hasLsof = internals.hasLsof(); diff --git a/test/core.js b/test/core.js index fe4cc748e..195fb515e 100755 --- a/test/core.js +++ b/test/core.js @@ -11,7 +11,6 @@ const Stream = require('stream'); const TLS = require('tls'); const Boom = require('@hapi/boom'); -const Bounce = require('@hapi/bounce'); const CatboxMemory = require('@hapi/catbox-memory'); const Code = require('@hapi/code'); const Handlebars = require('handlebars'); @@ -22,6 +21,7 @@ const Lab = require('@hapi/lab'); const Vision = require('@hapi/vision'); const Wreck = require('@hapi/wreck'); +const Common = require('./common'); const internals = {}; @@ -1679,7 +1679,7 @@ describe('Core', () => { expect(res.result.isBoom).to.equal(true); }); - it('cleans unused file stream when response is overridden', { skip: process.platform === 'win32' }, async () => { + it('cleans unused file stream when response is overridden', { skip: !Common.hasLsof }, async () => { const server = Hapi.server(); await server.register(Inert); @@ -1702,12 +1702,6 @@ describe('Core', () => { const cmd = ChildProcess.spawn('lsof', ['-p', process.pid]); let lsof = ''; - cmd.on('error', (err) => { - - // Allow the test to pass on platforms with no lsof - Bounce.ignore(err, { errno: 'ENOENT' }); - }); - cmd.stdout.on('data', (buffer) => { lsof += buffer.toString(); diff --git a/test/request.js b/test/request.js index adf748a48..5e1e61c85 100755 --- a/test/request.js +++ b/test/request.js @@ -4,6 +4,7 @@ const Http = require('http'); const Net = require('net'); const Stream = require('stream'); const Url = require('url'); +const Events = require('events'); const Boom = require('@hapi/boom'); const Code = require('@hapi/code'); @@ -275,45 +276,57 @@ describe('Request', () => { describe('active()', () => { - it('exits handler early when request is no longer active', async () => { + it('exits handler early when request is no longer active', { retry: true }, async (flags) => { + + let testComplete = false; + + const onCleanup = []; + flags.onCleanup = async () => { + + testComplete = true; + + for (const cleanup of onCleanup) { + await cleanup(); + } + }; const server = Hapi.server(); - const team = new Teamwork.Team(); - const team2 = new Teamwork.Team(); + const leaveHandlerTeam = new Teamwork.Team(); - let rounds = 0; server.route({ method: 'GET', path: '/', options: { handler: async (request, h) => { - team2.attend(); - for (let i = 0; i < 100; ++i) { - ++rounds; - await Hoek.wait(10); + req.abort(); - if (!request.active()) { - break; - } + while (request.active() && !testComplete) { + await Hoek.wait(10); } - team.attend(); + leaveHandlerTeam.attend({ + active: request.active(), + testComplete + }); + return null; } } }); await server.start(); + onCleanup.unshift(() => server.stop()); - const req = Http.get(server.info.uri, (res) => { }); + const req = Http.get(server.info.uri, Hoek.ignore); req.on('error', Hoek.ignore); - await team2.work; - req.abort(); - await server.stop(); - await team.work; - expect(rounds).to.be.below(10); + const note = await leaveHandlerTeam.work; + + expect(note).to.equal({ + active: false, + testComplete: false + }); }); }); @@ -847,10 +860,10 @@ describe('Request', () => { describe('_postCycle()', () => { - it('skips onPreResponse when validation terminates request', async () => { + it('skips onPreResponse when validation terminates request', { retry: true }, async (flags) => { const server = Hapi.server(); - const team = new Teamwork.Team(); + const abortedReqTeam = new Teamwork.Team(); let called = false; server.ext('onPreResponse', (request, h) => { @@ -863,14 +876,23 @@ describe('Request', () => { method: 'GET', path: '/', options: { - handler: () => null, + handler: (request) => { + + // Stash raw so that we can access it on response validation + Object.assign(request.app, request.raw); + + return null; + }, response: { status: { - 200: async () => { + 200: async (_, { context }) => { req.abort(); - await Hoek.wait(10); - team.attend(); + + const raw = context.app.request; + await Events.once(raw.req, 'aborted'); + + abortedReqTeam.attend(); } } } @@ -878,13 +900,14 @@ describe('Request', () => { }); await server.start(); + flags.onCleanup = () => server.stop(); - const req = Http.get(server.info.uri, (res) => { }); + const req = Http.get(server.info.uri, Hoek.ignore); req.on('error', Hoek.ignore); - await team.work; - await Hoek.wait(100); - await server.stop(); + await abortedReqTeam.work; + + await server.events.once('response'); expect(called).to.be.false(); }); @@ -2279,41 +2302,41 @@ describe('Request', () => { expect(res.statusCode).to.equal(200); }); - it('handles race condition between equal client and server timeouts', async () => { + it('handles race condition between equal client and server timeouts', async (flags) => { + + const onCleanup = []; + flags.onCleanup = async () => { + + for (const cleanup of onCleanup) { + await cleanup(); + } + }; const server = Hapi.server({ routes: { timeout: { server: 100 }, payload: { timeout: 100 } } }); server.route({ method: 'POST', path: '/timeout', options: { handler: Hoek.block } }); await server.start(); + onCleanup.unshift(() => server.stop()); const timer = new Hoek.Bench(); const options = { - hostname: '127.0.0.1', + hostname: 'localhost', port: server.info.port, path: '/timeout', method: 'POST' }; - await new Promise(async (resolve) => { + const req = Http.request(options); + onCleanup.unshift(() => req.destroy()); - const req = Http.request(options, (res) => { + req.write('\n'); - expect([503, 408]).to.contain(res.statusCode); - expect(timer.elapsed()).to.be.at.least(80); - resolve(); - }); + const [res] = await Events.once(req, 'response'); - req.on('error', (err) => { + expect([503, 408]).to.contain(res.statusCode); + expect(timer.elapsed()).to.be.at.least(80); - expect(err).to.not.exist(); - }); - - req.write('\n'); - await Hoek.wait(200); - req.end(); - }); - - await server.stop({ timeout: 1 }); + await Events.once(req, 'close'); // Ensures that req closes without error }); }); diff --git a/test/transmit.js b/test/transmit.js index 63b269e11..d77cc691e 100755 --- a/test/transmit.js +++ b/test/transmit.js @@ -8,7 +8,6 @@ const Stream = require('stream'); const Zlib = require('zlib'); const Boom = require('@hapi/boom'); -const Bounce = require('@hapi/bounce'); const Code = require('@hapi/code'); const Hapi = require('..'); const Hoek = require('@hapi/hoek'); @@ -17,6 +16,7 @@ const Lab = require('@hapi/lab'); const Teamwork = require('@hapi/teamwork'); const Wreck = require('@hapi/wreck'); +const Common = require('./common'); const internals = {}; @@ -86,7 +86,7 @@ describe('transmission', () => { expect(res.statusCode).to.equal(200); }); - it('closes file handlers when not reading file stream', { skip: process.platform === 'win32' }, async () => { + it('closes file handlers when not reading file stream', { skip: !Common.hasLsof }, async () => { const server = Hapi.server(); await server.register(Inert); @@ -101,12 +101,6 @@ describe('transmission', () => { const cmd = ChildProcess.spawn('lsof', ['-p', process.pid]); let lsof = ''; - cmd.on('error', (err) => { - - // Allow the test to pass on platforms with no lsof - Bounce.ignore(err, { errno: 'ENOENT' }); - }); - cmd.stdout.on('data', (buffer) => { lsof += buffer.toString(); @@ -128,7 +122,7 @@ describe('transmission', () => { }); }); - it('closes file handlers when not using a manually open file stream', { skip: process.platform === 'win32' }, async () => { + it('closes file handlers when not using a manually open file stream', { skip: !Common.hasLsof }, async () => { const server = Hapi.server(); server.route({ method: 'GET', path: '/file', handler: (request, h) => h.response(Fs.createReadStream(__dirname + '/../package.json')).header('etag', 'abc') }); @@ -142,12 +136,6 @@ describe('transmission', () => { const cmd = ChildProcess.spawn('lsof', ['-p', process.pid]); let lsof = ''; - cmd.on('error', (err) => { - - // Allow the test to pass on platforms with no lsof - Bounce.ignore(err, { errno: 'ENOENT' }); - }); - cmd.stdout.on('data', (buffer) => { lsof += buffer.toString();