From 67a589be8b103d55104f74e3dbccf9ab883552e5 Mon Sep 17 00:00:00 2001 From: Andrew Wilcox Date: Thu, 21 Nov 2013 13:03:04 -0500 Subject: [PATCH] Fix session handle tests so that multiple copies of the test can run at the same time. --- packages/livedata/livedata_server_tests.js | 219 ++++++++++++++------- 1 file changed, 148 insertions(+), 71 deletions(-) diff --git a/packages/livedata/livedata_server_tests.js b/packages/livedata/livedata_server_tests.js index 74561e9ef8..a94560e902 100644 --- a/packages/livedata/livedata_server_tests.js +++ b/packages/livedata/livedata_server_tests.js @@ -1,60 +1,135 @@ var Fiber = Npm.require('fibers'); -Tinytest.addAsync( - "livedata server - sessionHandle.onClose()", - function (test, onComplete) { - var connection; - var callbackHandle = Meteor.onConnection(function (sessionHandle) { - callbackHandle.stop(); - test.isTrue(_.isString(sessionHandle.id), "sessionHandle.id exists and is a string"); - // On the server side, wait for the connection to be closed. - sessionHandle.onClose(function () { - onComplete(); - }); - // Close the connection from the client. - connection.disconnect(); - }); - connection = DDP.connect(Meteor.absoluteUrl()); - } -); - // like pollUntil but doesn't have to be called from testAsyncMulti. -var poll = function (test, onComplete, fn) { +// +// Call `fn` periodically until it returns true. If it does, call +// `success`. If it doesn't before the timeout, call `failed`. +// +// An implementation that used fibers would be easier to use, but +// don't want to rule out the possibility of eventually also running +// these tests from the client (which would need an additional +// signaling mechanism to tell the server when to do particular steps +// such as closing the connection on the server side). +var poll = function (fn, success, failed) { var timeout = 10000; var step = 200; var start = (new Date()).valueOf(); var helper = function () { if (fn()) { - test.ok(); - onComplete(); + success(); return; } if (start + timeout < (new Date()).valueOf()) { - test.fail(); - onComplete(); + failed(); return; } Meteor.setTimeout(helper, step); }; helper(); }; - -Tinytest.addAsync("livedata server - sessionHandle.close()", function (test, onComplete) { + + +// Establish a connection from the server to the server, and wait +// until the client side of the connection has received the session +// id. On success call `succeeded` with two arguments, the client +// side `connection` and the server side `session`. Call `failed` on +// failure. +var establishConnection = function (test, succeeded, failed) { + // The connection from the client side. var connection; - var callbackHandle = Meteor.onConnection(function (sessionHandle) { - callbackHandle.stop(); - poll(test, onComplete, function () { - return ! connection.status().connected; - }); + // Track incoming sessions server side until we know which one is + // ours. + var sessions = {}; - // Close the connection from the server. - sessionHandle.close(); + // Add incoming sessions to `sessions`. + var onConnectionHandle = Meteor.onConnection(function (session) { + test.isTrue(_.isString(session.id), "session handle id exists and is a string"); + if (sessions[session.id]) { + test.fail("onConnection callback called multiple times for same session id"); + failed(); + } + else { + sessions[session.id] = session; + } }); - connection = DDP.connect(Meteor.absoluteUrl(), {retry: false}); -}); + // We've succeeded when we get the session id on the client side. + var onClientSessionId = function (sessionId) { + test.isTrue(connection.status().connected); + var session = sessions[sessionId]; + if (! session) { + test.fail("No onConnection received server side for connected client"); + failed(); + } + else { + onConnectionHandle.stop(); + succeeded(connection, session); + } + }; + // Connect and wait until the connection receives its session id. + // Disable retries so that when the connection is closed we don't + // automatically keep reconnecting on the client side. + connection = DDP.connect(Meteor.absoluteUrl(), {retry: false}); + poll( + function () { + return connection._lastSessionId; + }, + function () { + onClientSessionId(connection._lastSessionId); + }, + function () { + test.fail("client side of connection did not receive a session id"); + failed(); + } + ); +}; + +Tinytest.addAsync( + "livedata server - sessionHandle.onClose()", + function (test, onComplete) { + establishConnection( + test, + function (connection, session) { + // On the server side, wait for the connection to be closed. + session.onClose(function () { + onComplete(); + }); + // Close the connection from the client. + connection.disconnect(); + }, + onComplete + ); + } +); + + +Tinytest.addAsync( + "livedata server - sessionHandle.close()", + function (test, onComplete) { + establishConnection( + test, + function (connection, session) { + // Wait for the connection to be closed from the server side. + poll( + function () { + return ! connection.status().connected; + }, + onComplete, + function () { + test.fail("timeout waiting for the connection to be closed on the server side"); + onComplete(); + } + ); + + // Close the connection from the server. + session.close(); + }, + onComplete + ); + } +); var innerCalled = null; @@ -74,20 +149,20 @@ Meteor.methods({ Tinytest.addAsync( - "livedata server - sessionId in method invocation", + "livedata server - session in method invocation", function (test, onComplete) { - var sessionId; - var callbackHandle = Meteor.onConnection(function (sessionHandle) { - callbackHandle.stop(); - sessionId = sessionHandle.id; - }); - innerCalled = function (methodInvocation) { - test.equal(methodInvocation.session.id, sessionId); - onComplete(); - }; - var connection = DDP.connect(Meteor.absoluteUrl()); - connection.call('livedata_server_test_inner'); - connection.disconnect(); + establishConnection( + test, + function (connection, session) { + innerCalled = function (methodInvocation) { + test.equal(methodInvocation.session.id, session.id); + onComplete(); + }; + connection.call('livedata_server_test_inner'); + connection.disconnect(); + }, + onComplete + ); } ); @@ -95,35 +170,37 @@ Tinytest.addAsync( Tinytest.addAsync( "livedata server - session in nested method invocation", function (test, onComplete) { - var sessionId; - var callbackHandle = Meteor.onConnection(function (sessionHandle) { - callbackHandle.stop(); - sessionId = sessionHandle.id; - }); - innerCalled = function (methodInvocation) { - test.equal(methodInvocation.session.id, sessionId); - onComplete(); - }; - var connection = DDP.connect(Meteor.absoluteUrl()); - connection.call('livedata_server_test_outer'); - connection.disconnect(); + establishConnection( + test, + function (connection, session) { + innerCalled = function (methodInvocation) { + test.equal(methodInvocation.session.id, session.id); + onComplete(); + }; + connection.call('livedata_server_test_outer'); + connection.disconnect(); + }, + onComplete + ); } ); - + Tinytest.addAsync( "livedata server - session data in nested method invocation", function (test, onComplete) { - var callbackHandle = Meteor.onConnection(function (session) { - callbackHandle.stop(); - session._sessionData.foo = 123; - }); - innerCalled = function (methodInvocation) { - test.equal(methodInvocation._sessionData.foo, 123); - onComplete(); - }; - var connection = DDP.connect(Meteor.absoluteUrl()); - connection.call('livedata_server_test_outer'); - connection.disconnect(); + establishConnection( + test, + function (connection, session) { + session._sessionData.foo = 123; + innerCalled = function (methodInvocation) { + test.equal(methodInvocation._sessionData.foo, 123); + onComplete(); + }; + connection.call('livedata_server_test_outer'); + connection.disconnect(); + }, + onComplete + ); } );