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