Monday, June 6, 2011

Vows for SPDY Push

‹prev | My Chain | next›

I am about ready to pull back from my SPDY server push spike and implement for real in node-spdy. But it has been a while since I have done serious vows.js testing so this may take a bit.

Actually, it may not. When I run the existing test suite for node-spdy, I get:
  node-spdy git:(push-stream) vows test/spdy-basic-test.js --spec

SPDY/basic test

spdy.createServer
should return spdy.Server instance
Listening on this server instance
should be successfull
Calling spdy.createZLib
should return instance of spdy.ZLib
Creating new connection to this server
should receive connect event
Creating parser and waiting for SYN_REPLY
should end up w/ that frame
Sending control SYN_STREAM frame should be successfull and sending request body
should emit data and end events on request
Creating parser and waiting for Data packet
should end up w/ that frame
» expected 1,
got 0 (==) // spdy-basic-test.js:140

should end up w/ that frame
When calling server.close
all connections will be dropped

Broken » 7 honored 1 broken (0.036s)
The test structure for this basic test is... interesting. Most of the topics and vows are setting test-local variables to be re-used later on in the test suite. For instance, the connection variable is declared outside of the test suite:
var spdy = require('../lib/spdy');

var server,
connection,
//...
And is assigned inside the "Creating new connection" topic:
vows.describe('SPDY/basic test').addBatch({
'spdy.createServer': {
//...
}
}).addBatch({
'Listening on this server instance': {
//...
}
}).addBatch({
'Creating new connection to this server': {
topic: function() {
connection = require('tls').connect(PORT, 'localhost', options, this.callback);
},
'should receive connect event': function() {
}
},
'Calling spdy.createZLib': {
topic: function() {
return spdy.createZLib();
},
'should return instance of spdy.ZLib': function(_zlib) {
zlib = _zlib;
assert.instanceOf(zlib, spdy.ZLib);
}
}
}).addBatch({
//...
Later on there is a separate topic that pipes that connection directly into a node-spdy Parser object:
//...
'Creating parser': {
topic: function() {
var parser = spdy.createParser(zlib);

connection.pipe(parser);

return parser;
},
//...
The failure that I am seeing is occurring in one of the two sub-contexts for this parser:
//...
'Creating parser': {
//...
'and waiting for SYN_REPLY': {
//...
},
'and waiting for Data packet': {
//...
}
}
//...
It is in the second sub-context, the waiting for the Data packet context, in which I am experiencing problems. Specifically, I am getting a 1 for 0 error:
  Creating parser and waiting for Data packet
should end up w/ that frame
» expected 1,
got 0 (==) // spdy-basic-test.js:137
The value of 1 is setting the expectation that there is a data FIN flag set on the first data frame received. But clearly that is not the case now.

The key word in that last sentence is "now". The data frame being received is the server push frame. I am sending out a separate, empty data FIN frame for the server push, so the first data push frame is devoid of FIN flags, hence the error.

Hrm... I am not quite ready to abandon my spike, but hate to proceed with that test failing. Actually, this is probably a good time to break out a separate vows suite for server push itself. From a high level, I expect the test to be exactly the same as this one, except that there is no FIN on the data packet:
//...
'should end up with the push frame': function(dframe) {
assert.ok(!dframe.headers.c);
assert.equal(dframe.headers.flags & spdy.enums.DATA_FLAG_FIN, 0);
}
//...
I save that into a separate test/spdy-push-test.js and run it to find:
...
Creating parser and waiting for Data packet
should end up with the push frame
should end up with the push frame
» expected 0,
got 1 (==) // spdy-push-test.js:143
...
Hunh? How could it fail in both suites? Ah, I see. It is getting called twice. The first time through it passes, but the second time through, it fails. I only want to call it once, so I create a closure in the topic to only call it a single time:
      topic: function(parser) {
var callback = this.callback;
var called = false;
parser.on('dframe', function(dframe) {
if (!called) {
callback(null, dframe);
}
called = true;

});
},
With that, I have my passing suite:
  node-spdy git:(push-stream)  vows test/spdy-push-test.js --spec

SPDY/basic test

spdy.createServer
should return spdy.Server instance
Listening on this server instance
should be successfull
Calling spdy.createZLib
should return instance of spdy.ZLib
Creating new connection to this server
should receive connect event
Creating parser and waiting for SYN_REPLY
should end up w/ that frame
Sending control SYN_STREAM frame should be successfull and sending request body
should emit data and end events on request
Creating parser and waiting for Data packet
should end up with the push frame
When calling server.close
all connections will be dropped

OK » 8 honored (0.033s)
I will pick back up there tomorrow trying to get both suites passing at the same time. If I can do that, I ought to have a fairly re-usable solution for SPDY server push. Of course, that is a big "if".



Day #42

No comments:

Post a Comment