From 1acafbdcd41b747d7ad02267e0dbda48fd0df073 Mon Sep 17 00:00:00 2001 From: squirrelfm Date: Fri, 21 Feb 2014 21:52:20 +0400 Subject: [PATCH] Added tests --- Gruntfile.js | 4 +- lib/client/index.spec.js | 115 ++++++++++++++++++ .../goaway-unknown-error-code.spec.js | 60 +++++++++ .../goaway-without-error-code.spec.js | 58 +++++++++ .../goaway-without-last-stream.spec.js | 57 +++++++++ .../success_cases/invalid-settings-id.spec.js | 62 ++++++++++ .../non-zero-reserve-bit.spec.js | 62 ++++++++++ .../send-headedrs-cookie-success.spec.js | 48 ++++++++ .../send-headedrs-success.spec.js | 46 +++++++ lib/client/testBootstrapper.js | 73 +++++++++++ .../tests/framing/oversized-data-test.js | 41 +++++++ lib/server/index.js | 2 +- lib/server/server.js | 2 +- package.json | 3 +- 14 files changed, 628 insertions(+), 5 deletions(-) create mode 100644 lib/client/index.spec.js create mode 100644 lib/client/success_cases/goaway-unknown-error-code.spec.js create mode 100644 lib/client/success_cases/goaway-without-error-code.spec.js create mode 100644 lib/client/success_cases/goaway-without-last-stream.spec.js create mode 100644 lib/client/success_cases/invalid-settings-id.spec.js create mode 100644 lib/client/success_cases/non-zero-reserve-bit.spec.js create mode 100644 lib/client/success_cases/send-headedrs-cookie-success.spec.js create mode 100644 lib/client/success_cases/send-headedrs-success.spec.js create mode 100644 lib/client/testBootstrapper.js create mode 100644 lib/client/tests/framing/oversized-data-test.js diff --git a/Gruntfile.js b/Gruntfile.js index ee568c5..41757a8 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -12,10 +12,10 @@ module.exports = function(grunt) { mochaTest: { options: { reporter: 'dot', - timeout: 2000 + timeout: 8000 }, client: { - src: ['./lib/client/index.js'] + src: ['./lib/client/**/*.spec.js'] }, server: { src: ['./lib/server/index.js'] diff --git a/lib/client/index.spec.js b/lib/client/index.spec.js new file mode 100644 index 0000000..aa02029 --- /dev/null +++ b/lib/client/index.spec.js @@ -0,0 +1,115 @@ +/* +Copyright © Microsoft Open Technologies, Inc. +All Rights Reserved +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache 2 License for the specific language governing permissions and limitations under the License. +*/ + +/* +The MIT License + +Copyright (C) 2013 Gábor Molnár + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + + +var glob = require('glob').sync, + expect = require('chai').expect, + bunyan = require('bunyan'), + path = require('path'), + tls = require('tls'), + fs = require('fs'), + http2Protocol = require('http2-protocol'), + net = require('net'), + portfinder = require('portfinder'); + +var Browser = require('./browser'); + +var implementedVersion = 'HTTP-draft-07/2.0'; + +describe('HTTP/2 client', function() { + + + var tests = glob(__dirname + '/tests/**/*-test.js'), + port = 80, + testServer, log, browser; + + function createLogger(name) { + return bunyan.createLogger({ + name: name, + streams: [{ + level: 'debug', + path: __dirname + '/../../test.log' + }], + serializers: http2Protocol.serializers, + level: 'info' + }); + } + + log = createLogger('test'); + beforeEach(function() { + // Load the TCP Library + + + // Keep track of the chat clients + var clients = []; + + // Start a TCP Server + testServer = net.createServer(); + + portfinder.getPort(function (err, newPort) { + port = newPort; + testServer.listen(port); + }); + + browser = new Browser(process.env.HTTP2_BROWSER); + }); + + + tests.forEach(function(file) { + it(file, function(done) { + + testServer.on('listening', function() { + browser.start('http://localhost:' + port); + }); + + testServer.on('connection', function(socket) { + testServer.close(); + var startTest = require(file); + startTest(socket, log, function(error) { + done(error); + socket.destroy(); + }); + }); + + }); + }); + + afterEach(function() { + browser.stop(); + }); + +}); \ No newline at end of file diff --git a/lib/client/success_cases/goaway-unknown-error-code.spec.js b/lib/client/success_cases/goaway-unknown-error-code.spec.js new file mode 100644 index 0000000..841d01d --- /dev/null +++ b/lib/client/success_cases/goaway-unknown-error-code.spec.js @@ -0,0 +1,60 @@ +describe('HTTP/2 client', function () { + + var http2 = require('http2-protocol'); + var testBootstrapper = require('../testBootstrapper'); + var tlsSocket; + + var testFunc = function (socket, log, callback, frame) { + tlsSocket = socket; + var endpoint = new http2.Endpoint(log, 'SERVER', {}); + socket.pipe(endpoint).pipe(socket); + var commonError; + + endpoint.on('stream', function (stream) { + frame = frame || { + type : 'GOAWAY', + flags : {}, + last_stream : 1, + error : 'NO_ERROR' + }; + + frame.stream = 0; + + testBootstrapper.withMethodSubstitution(Object.getPrototypeOf(endpoint._serializer).constructor, 'GOAWAY', + function (frame, buffers) { + var buffer = new Buffer(8); + var last_stream = frame.last_stream; + buffer.writeUInt32BE(last_stream, 0); + buffer.writeUInt32BE(30, 4); + buffers.push(buffer); + }, + function () { + endpoint._compressor.write(frame); + } + ); + + setTimeout(function () { + // If there are no exception until this, then we're done + if (commonError === undefined) { + console.error('Sent without errors'); + callback(); + } else { + console.error(commonError); + callback(commonError); + } + }, 2000); + }); + + endpoint._connection.on('peerError', function (error) { + commonError = error; + }); + + }; + + it(__filename, function (done) { + testBootstrapper(testFunc, function (error) { + done(error); + }); + }); + +}); diff --git a/lib/client/success_cases/goaway-without-error-code.spec.js b/lib/client/success_cases/goaway-without-error-code.spec.js new file mode 100644 index 0000000..801bb76 --- /dev/null +++ b/lib/client/success_cases/goaway-without-error-code.spec.js @@ -0,0 +1,58 @@ +describe('HTTP/2 client', function () { + + var http2 = require('http2-protocol'); + var testBootstrapper = require('../testBootstrapper'); + var tlsSocket; + + var testFunc = function (socket, log, callback, frame) { + tlsSocket = socket; + var endpoint = new http2.Endpoint(log, 'SERVER', {}); + socket.pipe(endpoint).pipe(socket); + var commonError; + + endpoint.on('stream', function (stream) { + frame = frame || { + type : 'GOAWAY', + flags : {}, + last_stream : 1 + }; + + frame.stream = 0; + + testBootstrapper.withMethodSubstitution(Object.getPrototypeOf(endpoint._serializer).constructor, 'GOAWAY', + function (frame, buffers) { + var buffer = new Buffer(8); + var last_stream = frame.last_stream; + buffer.writeUInt32BE(last_stream, 0); + buffers.push(buffer); + }, + function () { + endpoint._compressor.write(frame); + } + ); + + + setTimeout(function () { + // If there are no exception until this, then we're done + if (commonError === undefined) { + callback(); + } else { + console.error(commonError); + callback(commonError); + } + }, 2000); + }); + + endpoint._connection.on('peerError', function (error) { + commonError = error; + }); + + }; + + it(__filename, function (done) { + testBootstrapper(testFunc, function (error) { + done(error); + }); + }); + +}); diff --git a/lib/client/success_cases/goaway-without-last-stream.spec.js b/lib/client/success_cases/goaway-without-last-stream.spec.js new file mode 100644 index 0000000..d60ba46 --- /dev/null +++ b/lib/client/success_cases/goaway-without-last-stream.spec.js @@ -0,0 +1,57 @@ +describe('HTTP/2 client', function () { + + var http2 = require('http2-protocol'); + var testBootstrapper = require('../testBootstrapper'); + var tlsSocket; + + var testFunc = function (socket, log, callback, frame) { + tlsSocket = socket; + var endpoint = new http2.Endpoint(log, 'SERVER', {}); + socket.pipe(endpoint).pipe(socket); + var commonError; + + endpoint.on('stream', function (stream) { + frame = frame || { + type : 'GOAWAY', + flags : {}, + error : 'NO_ERROR' + }; + + frame.stream = 0; + + testBootstrapper.withMethodSubstitution(Object.getPrototypeOf(endpoint._serializer).constructor, 'GOAWAY', + function (frame, buffers) { + var buffer = new Buffer(8); + buffer.writeUInt32BE(0, 0); + buffer.writeUInt32BE(1, 4); + buffers.push(buffer); + }, + function () { + endpoint._compressor.write(frame); + } + ); + + setTimeout(function () { + // If there are no exception until this, then we're done + if (commonError === undefined) { + callback(); + } else { + console.error(commonError); + callback(commonError); + } + }, 2000); + }); + + endpoint._connection.on('peerError', function (error) { + commonError = error; + }); + + }; + + it(__filename, function (done) { + testBootstrapper(testFunc, function (error) { + done(error); + }); + }); + +}); diff --git a/lib/client/success_cases/invalid-settings-id.spec.js b/lib/client/success_cases/invalid-settings-id.spec.js new file mode 100644 index 0000000..e2cf724 --- /dev/null +++ b/lib/client/success_cases/invalid-settings-id.spec.js @@ -0,0 +1,62 @@ +describe('HTTP/2 client', function () { + + var http2 = require('http2-protocol'); + var testBootstrapper = require('../testBootstrapper'); + var tlsSocket; + + var testFunc = function (socket, log, callback, frame) { + tlsSocket = socket; + var endpoint = new http2.Endpoint(log, 'SERVER', {}); + socket.pipe(endpoint).pipe(socket); + var commonError; + + testBootstrapper.withMethodSubstitution(Object.getPrototypeOf(endpoint._serializer).constructor, 'SETTINGS', + function (frame, buffers) { + var buffer = new Buffer(1 * 8); + for (var i = 0; i < 1; i++) { + //Write settings with undefined ID 13 + buffer.writeUInt32BE(13 & 0xffffff, i * 8); + buffer.writeUInt32BE(13, i * 8 + 4); + } + + buffers.push(buffer); + }, + function (stream) { + frame = { + type : 'SETTINGS', + flags : {}, + settings : { + SETTINGS_MAX_CONCURRENT_STREAMS : 100 + } + }; + + frame.stream = 0; + + endpoint._compressor.write(frame); + + setTimeout(function () { + // If there are no exception until this, then we're done + if (commonError === undefined) { + console.error('Sent without errors'); + callback(); + } else { + console.error(commonError); + callback(commonError); + } + }, 2000); + } + ); + + endpoint._connection.on('peerError', function (error) { + commonError = error; + }); + + }; + + it(__filename, function (done) { + testBootstrapper(testFunc, function (error) { + done(error); + }); + }); + +}); diff --git a/lib/client/success_cases/non-zero-reserve-bit.spec.js b/lib/client/success_cases/non-zero-reserve-bit.spec.js new file mode 100644 index 0000000..440fbee --- /dev/null +++ b/lib/client/success_cases/non-zero-reserve-bit.spec.js @@ -0,0 +1,62 @@ +describe('HTTP/2 client', function () { + + var http2 = require('http2-protocol'); + var testBootstrapper = require('../testBootstrapper'); + var tlsSocket; + + var testFunc = function (socket, log, callback, frame) { + tlsSocket = socket; + var endpoint = new http2.Endpoint(log, 'SERVER', {}); + socket.pipe(endpoint).pipe(socket); + var commonError; + + frame = frame || { + type : 'DATA', + flags : {}, + data : new Buffer(10) + }; + + setImmediate(function () { + endpoint._serializer._sizeLimit = Infinity; + testBootstrapper.withMethodSubstitution(Object.getPrototypeOf(endpoint._serializer).constructor, 'commonHeader', + function (frame, buffers) { + var headerBuffer = new Buffer(8); + var size = 0; + for (var i = 0; i < buffers.length; i++) { + size += buffers[i].length; + } + headerBuffer.writeUInt16BE(size, 0); + headerBuffer.writeUInt8(0x0, 2); + var flagByte = 1; // non-zero flags + headerBuffer.writeUInt8(flagByte, 3); + headerBuffer.writeUInt32BE(1, 4); + buffers.unshift(headerBuffer); + }, + function () { + endpoint._compressor.write(frame); + } + ); + }); + + setTimeout(function () { + if (commonError === undefined) { + console.error('Sent without errors'); + callback(); + } else { + console.error(commonError); + callback(commonError); + } + }, 2000); + + endpoint._connection.on('peerError', function (error) { + commonError = error; + }); + }; + + it(__filename, function (done) { + testBootstrapper(testFunc, function (error) { + done(error); + }); + }); + +}); diff --git a/lib/client/success_cases/send-headedrs-cookie-success.spec.js b/lib/client/success_cases/send-headedrs-cookie-success.spec.js new file mode 100644 index 0000000..47e3dc5 --- /dev/null +++ b/lib/client/success_cases/send-headedrs-cookie-success.spec.js @@ -0,0 +1,48 @@ +describe('HTTP/2 client', function () { + + var http2 = require('http2-protocol'); + var testBootstrapper = require('../testBootstrapper'); + var tlsSocket; + + var testFunc = function (socket, log, callback, frame, expectedError) { + tlsSocket = socket; + var endpoint = new http2.Endpoint(log, 'SERVER', {}); + socket.pipe(endpoint).pipe(socket); + var commonError; + + endpoint.on('stream', function (stream) { + endpoint._compressor.write({ + type : 'HEADERS', + stream : stream.id, + headers : { + ':status' : 200, + ':set-cookie' : 'cookie1,cookie2,cookie3' + } + }); + + console.error("HEADERS SENT"); + setTimeout(function () { + // If there are no exception until this, then we're done + if (commonError === undefined) { + console.error('Sent without errors'); + callback(); + } else { + console.error(commonError); + callback(commonError); + } + }, 2000); + }); + + endpoint._connection.on('peerError', function (error) { + commonError = error; + }); + + }; + + it(__filename, function (done) { + testBootstrapper(testFunc, function (error) { + done(error); + }); + }); + +}); diff --git a/lib/client/success_cases/send-headedrs-success.spec.js b/lib/client/success_cases/send-headedrs-success.spec.js new file mode 100644 index 0000000..6c8f7bf --- /dev/null +++ b/lib/client/success_cases/send-headedrs-success.spec.js @@ -0,0 +1,46 @@ +describe('HTTP/2 client', function () { + + var http2 = require('http2-protocol'); + var testBootstrapper = require('../testBootstrapper'); + var tlsSocket; + + var testFunc = function (socket, log, callback, frame, desiredError) { + tlsSocket = socket; + var endpoint = new http2.Endpoint(log, 'SERVER', {}); + socket.pipe(endpoint).pipe(socket); + var commonError; + + endpoint.on('stream', function (stream) { + endpoint._compressor.write({ + type : 'HEADERS', + stream : stream.id, + headers : { + ':status' : 200 + } + }); + + console.error("HEADERS SENT"); + setTimeout(function () { + // If there are no exception until this, then we're done + if (commonError === undefined) { + console.error('Sent without errors'); + callback(); + } else { + console.error(commonError); + callback(commonError); + } + }, 2000); + }); + + endpoint._connection.on('peerError', function (error) { + commonError = error; + }); + }; + + it(__filename, function (done) { + testBootstrapper(testFunc, function (error) { + done(error); + }); + }); + +}); diff --git a/lib/client/testBootstrapper.js b/lib/client/testBootstrapper.js new file mode 100644 index 0000000..a448e0d --- /dev/null +++ b/lib/client/testBootstrapper.js @@ -0,0 +1,73 @@ +var glob = require('glob').sync, +expect = require('chai').expect, +bunyan = require('bunyan'), +path = require('path'), +net = require('net'), +fs = require('fs'), +http2Protocol = require('http2-protocol'), +Browser = require('./browser'), +portfinder = require('portfinder'); + + +function TestBootstraper(startTest, callback) { + testServer = net.createServer(); + var port = 8088; + + portfinder.getPort(function (err, newPort) { + port = newPort; + testServer.listen(port); + }); + + testServer.on('listening', function() { + browser = new Browser(process.env.HTTP2_BROWSER); + browser.start('http://localhost:' + port); + }); + + testServer.on('connection', function (socket) { + testServer.close(); + + var originalCallback = callback; + callback = function(error) { + if(originalCallback) { + originalCallback(error); + } + originalCallback = null; + socket.destroy(); + }; + + socket.on('error', function (err) { + console.error('Socket error: ' + err ); + if(originalCallback) { + originalCallback(error); + } + originalCallback = null; + }); + + startTest(socket, createLogger('test'), callback); + }); +} + +TestBootstraper.withMethodSubstitution = function (obj, methodName, methodSubstitution, callback) { + var originalMethod = obj[methodName]; + try { + obj[methodName] = methodSubstitution; + callback( ); + } finally { + obj[methodName] = originalMethod; + } +}; + +module.exports = TestBootstraper; + +function createLogger(name) { + return bunyan.createLogger({ + name : name, + streams : [{ + level : 'debug', + path : __dirname + '/../../test.log' + } + ], + serializers : http2Protocol.serializers, + level : 'info' + }); +} diff --git a/lib/client/tests/framing/oversized-data-test.js b/lib/client/tests/framing/oversized-data-test.js new file mode 100644 index 0000000..6289bf5 --- /dev/null +++ b/lib/client/tests/framing/oversized-data-test.js @@ -0,0 +1,41 @@ +// [From the spec](http://tools.ietf.org/html/draft-ietf-httpbis-http2-06#section-6.7): +// +// DATA frames MUST be associated with a stream. If a DATA frame is +// received whose stream identifier field is 0x0, the recipient MUST +// respond with a connection error (Section 5.4.1) of type +// PROTOCOL_ERROR. + +var http2 = require('http2-protocol'); +module.exports = function(socket, log, callback, frame, desiredError) { + var endpoint = new http2.Endpoint(log, 'SERVER', {}); + socket.pipe(endpoint).pipe(socket); + + endpoint.on('stream', function(stream) { + endpoint._compressor.write({ + type: 'HEADERS', + flags: {}, + stream: stream.id, + headers: { + ':status': 200 + } + }); + + log.debug('Sending oversized DATA frame'); + + endpoint._serializer._sizeLimit = Infinity; + endpoint._serializer.write({ + type: 'DATA', + flags: {}, + stream: stream.id, + data: new Buffer(16389) + }); + }); + + endpoint._connection.on('peerError', function(error) { + if (error === 'FRAME_SIZE_ERROR') { + callback(); + } else { + callback('Not appropriate error code: ' + error); + } + }); +}; \ No newline at end of file diff --git a/lib/server/index.js b/lib/server/index.js index a12c0b0..099066b 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -49,7 +49,7 @@ var Server = require('./server'); describe('HTTP/2 server', function() { var tests = glob(__dirname + '/tests/**/*-test.js'), hostname = 'localhost', - port = 8080, + port = 8800, testClient, log, server; function createLogger(name) { diff --git a/lib/server/server.js b/lib/server/server.js index a1e7b29..7da91f4 100644 --- a/lib/server/server.js +++ b/lib/server/server.js @@ -105,7 +105,7 @@ function Server(port, log) { } Server.prototype.start = function(done) { - this.server.listen(port || 8080); + this.server.listen(port || 8800); this.log.debug('Server started'); done(); }; diff --git a/package.json b/package.json index 857c0e6..4c6d822 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "mocha": "~1.15.1", "glob": "~3.2.7", "chai": "~1.8.1", - "http2": "~2.2.0" + "http2": "~2.2.0", + "portfinder": "~0.2.1" }, "devDependencies": { "grunt-mocha-test": "~0.8.1",