testing/xpcshell/node-http2/node_modules/http2-protocol/test/stream.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/test/stream.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,415 @@
     1.4 +var expect = require('chai').expect;
     1.5 +var util = require('./util');
     1.6 +
     1.7 +var stream = require('../lib/stream');
     1.8 +var Stream = stream.Stream;
     1.9 +
    1.10 +function createStream() {
    1.11 +  var stream = new Stream(util.log);
    1.12 +  stream.upstream._window = Infinity;
    1.13 +  return stream;
    1.14 +}
    1.15 +
    1.16 +// Execute a list of commands and assertions
    1.17 +var recorded_events = ['state', 'error', 'window_update', 'headers', 'promise'];
    1.18 +function execute_sequence(stream, sequence, done) {
    1.19 +  if (!done) {
    1.20 +    done = sequence;
    1.21 +    sequence = stream;
    1.22 +    stream = createStream();
    1.23 +  }
    1.24 +
    1.25 +  var outgoing_frames = [];
    1.26 +
    1.27 +  var emit = stream.emit, events = [];
    1.28 +  stream.emit = function(name) {
    1.29 +    if (recorded_events.indexOf(name) !== -1) {
    1.30 +      events.push({ name: name, data: Array.prototype.slice.call(arguments, 1) });
    1.31 +    }
    1.32 +    return emit.apply(this, arguments);
    1.33 +  };
    1.34 +
    1.35 +  var commands = [], checks = [];
    1.36 +  sequence.forEach(function(step) {
    1.37 +    if ('method' in step || 'incoming' in step || 'outgoing' in step || 'wait' in step || 'set_state' in step) {
    1.38 +      commands.push(step);
    1.39 +    }
    1.40 +
    1.41 +    if ('outgoing' in step || 'event' in step || 'active' in step) {
    1.42 +      checks.push(step);
    1.43 +    }
    1.44 +  });
    1.45 +
    1.46 +  var activeCount = 0;
    1.47 +  function count_change(change) {
    1.48 +    activeCount += change;
    1.49 +  }
    1.50 +
    1.51 +  function execute(callback) {
    1.52 +    var command = commands.shift();
    1.53 +    if (command) {
    1.54 +      if ('method' in command) {
    1.55 +        var value = stream[command.method.name].apply(stream, command.method.arguments);
    1.56 +        if (command.method.ret) {
    1.57 +          command.method.ret(value);
    1.58 +        }
    1.59 +        execute(callback);
    1.60 +      } else if ('incoming' in command) {
    1.61 +        command.incoming.count_change = count_change;
    1.62 +        stream.upstream.write(command.incoming);
    1.63 +        execute(callback);
    1.64 +      } else if ('outgoing' in command) {
    1.65 +        outgoing_frames.push(stream.upstream.read());
    1.66 +        execute(callback);
    1.67 +      } else if ('set_state' in command) {
    1.68 +        stream.state = command.set_state;
    1.69 +        execute(callback);
    1.70 +      } else if ('wait' in command) {
    1.71 +        setTimeout(execute.bind(null, callback), command.wait);
    1.72 +      } else {
    1.73 +        throw new Error('Invalid command', command);
    1.74 +      }
    1.75 +    } else {
    1.76 +      setTimeout(callback, 5);
    1.77 +    }
    1.78 +  }
    1.79 +
    1.80 +  function check() {
    1.81 +    checks.forEach(function(check) {
    1.82 +      if ('outgoing' in check) {
    1.83 +        var frame = outgoing_frames.shift();
    1.84 +        for (var key in check.outgoing) {
    1.85 +          expect(frame).to.have.property(key).that.deep.equals(check.outgoing[key]);
    1.86 +        }
    1.87 +        count_change(frame.count_change);
    1.88 +      } else if ('event' in check) {
    1.89 +        var event = events.shift();
    1.90 +        expect(event.name).to.be.equal(check.event.name);
    1.91 +        check.event.data.forEach(function(data, index) {
    1.92 +          expect(event.data[index]).to.deep.equal(data);
    1.93 +        });
    1.94 +      } else if ('active' in check) {
    1.95 +        expect(activeCount).to.be.equal(check.active);
    1.96 +      } else {
    1.97 +        throw new Error('Invalid check', check);
    1.98 +      }
    1.99 +    });
   1.100 +    done();
   1.101 +  }
   1.102 +
   1.103 +  setImmediate(execute.bind(null, check));
   1.104 +}
   1.105 +
   1.106 +var example_frames = [
   1.107 +  { type: 'PRIORITY', flags: {}, priority: 1 },
   1.108 +  { type: 'WINDOW_UPDATE', flags: {}, settings: {} },
   1.109 +  { type: 'RST_STREAM', flags: {}, error: 'CANCEL' },
   1.110 +  { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
   1.111 +  { type: 'DATA', flags: {}, data: new Buffer(5) },
   1.112 +  { type: 'PUSH_PROMISE', flags: {}, headers: {}, promised_stream: new Stream(util.log) }
   1.113 +];
   1.114 +
   1.115 +var invalid_incoming_frames = {
   1.116 +  IDLE: [
   1.117 +    { type: 'DATA', flags: {}, data: new Buffer(5) },
   1.118 +    { type: 'PRIORITY', flags: {}, priority: 1 },
   1.119 +    { type: 'WINDOW_UPDATE', flags: {}, settings: {} },
   1.120 +    { type: 'PUSH_PROMISE', flags: {}, headers: {} },
   1.121 +    { type: 'RST_STREAM', flags: {}, error: 'CANCEL' }
   1.122 +  ],
   1.123 +  RESERVED_LOCAL: [
   1.124 +    { type: 'DATA', flags: {}, data: new Buffer(5) },
   1.125 +    { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
   1.126 +    { type: 'PUSH_PROMISE', flags: {}, headers: {} },
   1.127 +    { type: 'WINDOW_UPDATE', flags: {}, settings: {} }
   1.128 +  ],
   1.129 +  RESERVED_REMOTE: [
   1.130 +    { type: 'DATA', flags: {}, data: new Buffer(5) },
   1.131 +    { type: 'PRIORITY', flags: {}, priority: 1 },
   1.132 +    { type: 'PUSH_PROMISE', flags: {}, headers: {} },
   1.133 +    { type: 'WINDOW_UPDATE', flags: {}, settings: {} }
   1.134 +  ],
   1.135 +  OPEN: [
   1.136 +  ],
   1.137 +  HALF_CLOSED_LOCAL: [
   1.138 +  ],
   1.139 +  HALF_CLOSED_REMOTE: [
   1.140 +    { type: 'DATA', flags: {}, data: new Buffer(5) },
   1.141 +    { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
   1.142 +    { type: 'PUSH_PROMISE', flags: {}, headers: {} }
   1.143 +  ]
   1.144 +};
   1.145 +
   1.146 +var invalid_outgoing_frames = {
   1.147 +  IDLE: [
   1.148 +    { type: 'DATA', flags: {}, data: new Buffer(5) },
   1.149 +    { type: 'PRIORITY', flags: {}, priority: 1 },
   1.150 +    { type: 'WINDOW_UPDATE', flags: {}, settings: {} },
   1.151 +    { type: 'PUSH_PROMISE', flags: {}, headers: {} }
   1.152 +  ],
   1.153 +  RESERVED_LOCAL: [
   1.154 +    { type: 'DATA', flags: {}, data: new Buffer(5) },
   1.155 +    { type: 'PRIORITY', flags: {}, priority: 1 },
   1.156 +    { type: 'PUSH_PROMISE', flags: {}, headers: {} },
   1.157 +    { type: 'WINDOW_UPDATE', flags: {}, settings: {} }
   1.158 +  ],
   1.159 +  RESERVED_REMOTE: [
   1.160 +    { type: 'DATA', flags: {}, data: new Buffer(5) },
   1.161 +    { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
   1.162 +    { type: 'PUSH_PROMISE', flags: {}, headers: {} },
   1.163 +    { type: 'WINDOW_UPDATE', flags: {}, settings: {} }
   1.164 +  ],
   1.165 +  OPEN: [
   1.166 +  ],
   1.167 +  HALF_CLOSED_LOCAL: [
   1.168 +    { type: 'DATA', flags: {}, data: new Buffer(5) },
   1.169 +    { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
   1.170 +    { type: 'PUSH_PROMISE', flags: {}, headers: {} }
   1.171 +  ],
   1.172 +  HALF_CLOSED_REMOTE: [
   1.173 +  ],
   1.174 +  CLOSED: [
   1.175 +    { type: 'PRIORITY', flags: {}, priority: 1 },
   1.176 +    { type: 'WINDOW_UPDATE', flags: {}, settings: {} },
   1.177 +    { type: 'HEADERS', flags: {}, headers: {}, priority: undefined },
   1.178 +    { type: 'DATA', flags: {}, data: new Buffer(5) },
   1.179 +    { type: 'PUSH_PROMISE', flags: {}, headers: {}, promised_stream: new Stream(util.log) }
   1.180 +  ]
   1.181 +};
   1.182 +
   1.183 +describe('stream.js', function() {
   1.184 +  describe('Stream class', function() {
   1.185 +    describe('._transition(sending, frame) method', function() {
   1.186 +      it('should emit error, and answer RST_STREAM for invalid incoming frames', function() {
   1.187 +        Object.keys(invalid_incoming_frames).forEach(function(state) {
   1.188 +          invalid_incoming_frames[state].forEach(function(invalid_frame) {
   1.189 +            var stream = createStream();
   1.190 +            stream.state = state;
   1.191 +            expect(stream._transition.bind(stream, false, invalid_frame)).to.throw('Uncaught, unspecified "error" event.');
   1.192 +          });
   1.193 +        });
   1.194 +
   1.195 +        // CLOSED state as a result of incoming END_STREAM (or RST_STREAM)
   1.196 +        var stream = createStream();
   1.197 +        stream.headers({});
   1.198 +        stream.end();
   1.199 +        stream.upstream.write({ type: 'HEADERS', headers:{}, flags: { END_STREAM: true }, count_change: util.noop });
   1.200 +        example_frames.forEach(function(invalid_frame) {
   1.201 +          invalid_frame.count_change = util.noop;
   1.202 +          expect(stream._transition.bind(stream, false, invalid_frame)).to.throw('Uncaught, unspecified "error" event.');
   1.203 +        });
   1.204 +
   1.205 +        // CLOSED state as a result of outgoing END_STREAM
   1.206 +        var stream = createStream();
   1.207 +        stream.upstream.write({ type: 'HEADERS', headers:{}, flags: { END_STREAM: true }, count_change: util.noop });
   1.208 +        stream.headers({});
   1.209 +        stream.end();
   1.210 +        example_frames.slice(3).forEach(function(invalid_frame) {
   1.211 +          invalid_frame.count_change = util.noop;
   1.212 +          expect(stream._transition.bind(stream, false, invalid_frame)).to.throw('Uncaught, unspecified "error" event.');
   1.213 +        });
   1.214 +      });
   1.215 +      it('should throw exception for invalid outgoing frames', function() {
   1.216 +        Object.keys(invalid_outgoing_frames).forEach(function(state) {
   1.217 +          invalid_outgoing_frames[state].forEach(function(invalid_frame) {
   1.218 +            var stream = createStream();
   1.219 +            stream.state = state;
   1.220 +            expect(stream._transition.bind(stream, true, invalid_frame)).to.throw(Error);
   1.221 +          });
   1.222 +        });
   1.223 +      });
   1.224 +      it('should close the stream when there\'s an incoming or outgoing RST_STREAM', function() {
   1.225 +        [
   1.226 +          'RESERVED_LOCAL',
   1.227 +          'RESERVED_REMOTE',
   1.228 +          'OPEN',
   1.229 +          'HALF_CLOSED_LOCAL',
   1.230 +          'HALF_CLOSED_REMOTE'
   1.231 +        ].forEach(function(state) {
   1.232 +            [true, false].forEach(function(sending) {
   1.233 +              var stream = createStream();
   1.234 +              stream.state = state;
   1.235 +              stream._transition(sending, { type: 'RST_STREAM', flags: {} });
   1.236 +              expect(stream.state).to.be.equal('CLOSED');
   1.237 +            });
   1.238 +          });
   1.239 +      });
   1.240 +      it('should ignore any incoming frame after sending reset', function() {
   1.241 +        var stream = createStream();
   1.242 +        stream.reset();
   1.243 +        example_frames.forEach(stream._transition.bind(stream, false));
   1.244 +      });
   1.245 +      it('should ignore certain incoming frames after closing the stream with END_STREAM', function() {
   1.246 +        var stream = createStream();
   1.247 +        stream.upstream.write({ type: 'HEADERS', flags: { END_STREAM: true }, headers:{} });
   1.248 +        stream.headers({});
   1.249 +        stream.end();
   1.250 +        example_frames.slice(0,3).forEach(function(frame) {
   1.251 +          frame.count_change = util.noop;
   1.252 +          stream._transition(false, frame);
   1.253 +        });
   1.254 +      });
   1.255 +    });
   1.256 +  });
   1.257 +  describe('test scenario', function() {
   1.258 +    describe('sending request', function() {
   1.259 +      it('should trigger the appropriate state transitions and outgoing frames', function(done) {
   1.260 +        execute_sequence([
   1.261 +          { method  : { name: 'headers', arguments: [{ ':path': '/' }] } },
   1.262 +          { outgoing: { type: 'HEADERS', flags: { }, headers: { ':path': '/' } } },
   1.263 +          { event   : { name: 'state', data: ['OPEN'] } },
   1.264 +
   1.265 +          { wait    : 5 },
   1.266 +          { method  : { name: 'end', arguments: [] } },
   1.267 +          { event   : { name: 'state', data: ['HALF_CLOSED_LOCAL'] } },
   1.268 +          { outgoing: { type: 'DATA', flags: { END_STREAM: true  }, data: new Buffer(0) } },
   1.269 +
   1.270 +          { wait    : 10 },
   1.271 +          { incoming: { type: 'HEADERS', flags: { }, headers: { ':status': 200 } } },
   1.272 +          { incoming: { type: 'DATA'   , flags: { END_STREAM: true  }, data: new Buffer(5) } },
   1.273 +          { event   : { name: 'headers', data: [{ ':status': 200 }] } },
   1.274 +          { event   : { name: 'state', data: ['CLOSED'] } },
   1.275 +
   1.276 +          { active  : 0 }
   1.277 +        ], done);
   1.278 +      });
   1.279 +    });
   1.280 +    describe('answering request', function() {
   1.281 +      it('should trigger the appropriate state transitions and outgoing frames', function(done) {
   1.282 +        var payload = new Buffer(5);
   1.283 +        execute_sequence([
   1.284 +          { incoming: { type: 'HEADERS', flags: { }, headers: { ':path': '/' } } },
   1.285 +          { event   : { name: 'state', data: ['OPEN'] } },
   1.286 +          { event   : { name: 'headers', data: [{ ':path': '/' }] } },
   1.287 +
   1.288 +          { wait    : 5 },
   1.289 +          { incoming: { type: 'DATA', flags: { }, data: new Buffer(5) } },
   1.290 +          { incoming: { type: 'DATA', flags: { END_STREAM: true  }, data: new Buffer(10) } },
   1.291 +          { event   : { name: 'state', data: ['HALF_CLOSED_REMOTE'] } },
   1.292 +
   1.293 +          { wait    : 5 },
   1.294 +          { method  : { name: 'headers', arguments: [{ ':status': 200 }] } },
   1.295 +          { outgoing: { type: 'HEADERS', flags: { }, headers: { ':status': 200 } } },
   1.296 +
   1.297 +          { wait    : 5 },
   1.298 +          { method  : { name: 'end', arguments: [payload] } },
   1.299 +          { outgoing: { type: 'DATA', flags: { END_STREAM: true  }, data: payload } },
   1.300 +          { event   : { name: 'state', data: ['CLOSED'] } },
   1.301 +
   1.302 +          { active  : 0 }
   1.303 +        ], done);
   1.304 +      });
   1.305 +    });
   1.306 +    describe('sending push stream', function() {
   1.307 +      it('should trigger the appropriate state transitions and outgoing frames', function(done) {
   1.308 +        var payload = new Buffer(5);
   1.309 +        var pushStream;
   1.310 +
   1.311 +        execute_sequence([
   1.312 +          // receiving request
   1.313 +          { incoming: { type: 'HEADERS', flags: { END_STREAM: true }, headers: { ':path': '/' } } },
   1.314 +          { event   : { name: 'state', data: ['OPEN'] } },
   1.315 +          { event   : { name: 'state', data: ['HALF_CLOSED_REMOTE'] } },
   1.316 +          { event   : { name: 'headers', data: [{ ':path': '/' }] } },
   1.317 +
   1.318 +          // sending response headers
   1.319 +          { wait    : 5 },
   1.320 +          { method  : { name: 'headers', arguments: [{ ':status': '200' }] } },
   1.321 +          { outgoing: { type: 'HEADERS', flags: {  }, headers: { ':status': '200' } } },
   1.322 +
   1.323 +          // sending push promise
   1.324 +          { method  : { name: 'promise', arguments: [{ ':path': '/' }], ret: function(str) { pushStream = str; } } },
   1.325 +          { outgoing: { type: 'PUSH_PROMISE', flags: { }, headers: { ':path': '/' } } },
   1.326 +
   1.327 +          // sending response data
   1.328 +          { method  : { name: 'end', arguments: [payload] } },
   1.329 +          { outgoing: { type: 'DATA', flags: { END_STREAM: true  }, data: payload } },
   1.330 +          { event   : { name: 'state', data: ['CLOSED'] } },
   1.331 +
   1.332 +          { active  : 0 }
   1.333 +        ], function() {
   1.334 +          // initial state of the promised stream
   1.335 +          expect(pushStream.state).to.equal('RESERVED_LOCAL');
   1.336 +
   1.337 +          execute_sequence(pushStream, [
   1.338 +            // push headers
   1.339 +            { wait    : 5 },
   1.340 +            { method  : { name: 'headers', arguments: [{ ':status': '200' }] } },
   1.341 +            { outgoing: { type: 'HEADERS', flags: { }, headers: { ':status': '200' } } },
   1.342 +            { event   : { name: 'state', data: ['HALF_CLOSED_REMOTE'] } },
   1.343 +
   1.344 +            // push data
   1.345 +            { method  : { name: 'end', arguments: [payload] } },
   1.346 +            { outgoing: { type: 'DATA', flags: { END_STREAM: true  }, data: payload } },
   1.347 +            { event   : { name: 'state', data: ['CLOSED'] } },
   1.348 +
   1.349 +            { active  : 1 }
   1.350 +          ], done);
   1.351 +        });
   1.352 +      });
   1.353 +    });
   1.354 +    describe('receiving push stream', function() {
   1.355 +      it('should trigger the appropriate state transitions and outgoing frames', function(done) {
   1.356 +        var payload = new Buffer(5);
   1.357 +        var original_stream = createStream();
   1.358 +        var promised_stream = createStream();
   1.359 +
   1.360 +        done = util.callNTimes(2, done);
   1.361 +
   1.362 +        execute_sequence(original_stream, [
   1.363 +          // sending request headers
   1.364 +          { method  : { name: 'headers', arguments: [{ ':path': '/' }] } },
   1.365 +          { method  : { name: 'end', arguments: [] } },
   1.366 +          { outgoing: { type: 'HEADERS', flags: { END_STREAM: true  }, headers: { ':path': '/' } } },
   1.367 +          { event   : { name: 'state', data: ['OPEN'] } },
   1.368 +          { event   : { name: 'state', data: ['HALF_CLOSED_LOCAL'] } },
   1.369 +
   1.370 +          // receiving response headers
   1.371 +          { wait    : 10 },
   1.372 +          { incoming: { type: 'HEADERS', flags: { }, headers: { ':status': 200 } } },
   1.373 +          { event   : { name: 'headers', data: [{ ':status': 200 }] } },
   1.374 +
   1.375 +          // receiving push promise
   1.376 +          { incoming: { type: 'PUSH_PROMISE', flags: { }, headers: { ':path': '/2.html' }, promised_stream: promised_stream } },
   1.377 +          { event   : { name: 'promise', data: [promised_stream, { ':path': '/2.html' }] } },
   1.378 +
   1.379 +          // receiving response data
   1.380 +          { incoming: { type: 'DATA'   , flags: { END_STREAM: true  }, data: payload } },
   1.381 +          { event   : { name: 'state', data: ['CLOSED'] } },
   1.382 +
   1.383 +          { active  : 0 }
   1.384 +        ], done);
   1.385 +
   1.386 +        execute_sequence(promised_stream, [
   1.387 +          // initial state of the promised stream
   1.388 +          { event   : { name: 'state', data: ['RESERVED_REMOTE'] } },
   1.389 +
   1.390 +          // push headers
   1.391 +          { wait    : 10 },
   1.392 +          { incoming: { type: 'HEADERS', flags: { END_STREAM: false }, headers: { ':status': 200 } } },
   1.393 +          { event   : { name: 'state', data: ['HALF_CLOSED_LOCAL'] } },
   1.394 +          { event   : { name: 'headers', data: [{ ':status': 200 }] } },
   1.395 +
   1.396 +          // push data
   1.397 +          { incoming: { type: 'DATA', flags: { END_STREAM: true  }, data: payload } },
   1.398 +          { event   : { name: 'state', data: ['CLOSED'] } },
   1.399 +
   1.400 +          { active  : 0 }
   1.401 +        ], done);
   1.402 +      });
   1.403 +    });
   1.404 +  });
   1.405 +
   1.406 +  describe('bunyan formatter', function() {
   1.407 +    describe('`s`', function() {
   1.408 +      var format = stream.serializers.s;
   1.409 +      it('should assign a unique ID to each frame', function() {
   1.410 +        var stream1 = createStream();
   1.411 +        var stream2 = createStream();
   1.412 +        expect(format(stream1)).to.be.equal(format(stream1));
   1.413 +        expect(format(stream2)).to.be.equal(format(stream2));
   1.414 +        expect(format(stream1)).to.not.be.equal(format(stream2));
   1.415 +      });
   1.416 +    });
   1.417 +  });
   1.418 +});

mercurial