summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/UploadWizard/tests/qunit/uw.ConcurrentQueue.test.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/UploadWizard/tests/qunit/uw.ConcurrentQueue.test.js')
-rw-r--r--www/wiki/extensions/UploadWizard/tests/qunit/uw.ConcurrentQueue.test.js412
1 files changed, 412 insertions, 0 deletions
diff --git a/www/wiki/extensions/UploadWizard/tests/qunit/uw.ConcurrentQueue.test.js b/www/wiki/extensions/UploadWizard/tests/qunit/uw.ConcurrentQueue.test.js
new file mode 100644
index 00000000..8f9ec887
--- /dev/null
+++ b/www/wiki/extensions/UploadWizard/tests/qunit/uw.ConcurrentQueue.test.js
@@ -0,0 +1,412 @@
+/*
+ * This file is part of the MediaWiki extension UploadWizard.
+ *
+ * UploadWizard is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * UploadWizard is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+( function ( uw, $ ) {
+ QUnit.module( 'uw.ConcurrentQueue', QUnit.newMwEnvironment() );
+
+ // This is a bogus action that will be executed for every item added to the
+ // queue. We just need to make sure that the action doesn't complete
+ // immediately, or the order some methods are called in could be slightly
+ // different (e.g. when adding a new item after one has just completed will
+ // trigger the next one to execute, which would terminate immediately,
+ // instead of giving time for a second new thingy to be added)
+ function queueAction() {
+ var deferred = $.Deferred();
+ setTimeout( deferred.resolve, 10 );
+ return deferred.promise();
+ }
+
+ // Asserts that the given stub functions were called in the given order.
+ // SinonJS's assert.callOrder doesn't allow to check individual calls.
+ function assertCalledInOrder() {
+ var calls, i, currSpyCall, nextSpyCall;
+ // Map stubs to specific calls
+ calls = Array.prototype.map.call( arguments, function ( spy ) {
+ if ( !spy.assertCallsInOrderLastCall ) {
+ spy.assertCallsInOrderLastCall = 0;
+ }
+ return spy.getCall( spy.assertCallsInOrderLastCall++ );
+ } );
+ // Assert stuff
+ for ( i = 0; i < calls.length - 1; i++ ) {
+ currSpyCall = calls[ i ];
+ nextSpyCall = calls[ i + 1 ];
+ if ( currSpyCall ) {
+ QUnit.assert.ok(
+ currSpyCall.callId < ( nextSpyCall ? nextSpyCall.callId : -1 ),
+ 'Call ' + ( i + 1 ) + ' (callId ' + currSpyCall.callId + ') is in the right order'
+ );
+ } else {
+ QUnit.assert.ok( false, 'Call ' + ( i + 1 ) + ' (never called) is in the right order' );
+ }
+ }
+ QUnit.assert.ok(
+ nextSpyCall,
+ 'Call ' + calls.length + ' is in the right order'
+ );
+ }
+
+ QUnit.test( 'Basic behavior', function ( assert ) {
+ var done, action, queue;
+ done = assert.async();
+ action = sinon.spy( queueAction );
+ queue = new uw.ConcurrentQueue( {
+ count: 3,
+ action: action
+ } );
+
+ queue.on( 'progress', function () {
+ QUnit.assert.ok( queue.running.length <= 3, 'No more than 3 items are executing' );
+ } );
+
+ queue.on( 'complete', function () {
+ // All items executed
+ sinon.assert.callCount( action, 5 );
+ // All items executed in the expected order
+ sinon.assert.calledWith( action.getCall( 0 ), 'a' );
+ sinon.assert.calledWith( action.getCall( 1 ), 'b' );
+ sinon.assert.calledWith( action.getCall( 2 ), 'c' );
+ sinon.assert.calledWith( action.getCall( 3 ), 'd' );
+ sinon.assert.calledWith( action.getCall( 4 ), 'e' );
+
+ done();
+ } );
+
+ [ 'a', 'b', 'c', 'd', 'e' ].forEach( function ( v ) {
+ queue.addItem( v );
+ } );
+
+ queue.startExecuting();
+ } );
+
+ QUnit.test( 'Event emitting', function ( assert ) {
+ var done, changeHandler, progressHandler, completeHandler, queue;
+ done = assert.async();
+ changeHandler = sinon.stub();
+ progressHandler = sinon.stub();
+ completeHandler = sinon.stub();
+ queue = new uw.ConcurrentQueue( {
+ count: 3,
+ action: queueAction
+ } );
+
+ queue.connect( null, {
+ change: changeHandler,
+ progress: progressHandler,
+ complete: completeHandler
+ } );
+
+ queue.on( 'complete', function () {
+ sinon.assert.callCount( changeHandler, 3 );
+ sinon.assert.callCount( progressHandler, 3 );
+ sinon.assert.callCount( completeHandler, 1 );
+
+ assertCalledInOrder(
+ changeHandler, // Added 'a'
+ changeHandler, // Added 'b'
+ changeHandler, // Added 'c'
+ progressHandler, // Finished 'a', 'b' or 'c'
+ progressHandler, // Finished 'a', 'b' or 'c'
+ progressHandler, // Finished 'a', 'b' or 'c'
+ completeHandler
+ );
+
+ done();
+ } );
+
+ queue.addItem( 'a' );
+ queue.addItem( 'b' );
+ queue.addItem( 'c' );
+ queue.startExecuting();
+ } );
+
+ QUnit.test( 'Restarting a completed queue', function ( assert ) {
+ var done, queue;
+ done = assert.async();
+ queue = new uw.ConcurrentQueue( {
+ count: 3,
+ action: queueAction
+ } );
+
+ queue.addItem( 'a' );
+ queue.addItem( 'b' );
+ queue.addItem( 'c' );
+
+ queue.once( 'complete', function () {
+ QUnit.assert.equal( queue.completed, true );
+ queue.addItem( 'd' );
+ queue.addItem( 'e' );
+
+ queue.once( 'complete', function () {
+ QUnit.assert.equal( queue.completed, true );
+ done();
+ } );
+
+ queue.startExecuting();
+ } );
+
+ queue.startExecuting();
+ } );
+
+ QUnit.test( 'Empty queue completes', function ( assert ) {
+ var done, queue;
+ done = assert.async();
+ queue = new uw.ConcurrentQueue( {
+ count: 3,
+ action: queueAction
+ } );
+
+ queue.on( 'complete', function () {
+ QUnit.assert.equal( queue.completed, true );
+
+ done();
+ } );
+
+ queue.startExecuting();
+ } );
+
+ QUnit.test( 'Adding new items while queue running', function ( assert ) {
+ var done, changeHandler, progressHandler, completeHandler, queue;
+ done = assert.async();
+ changeHandler = sinon.stub();
+ progressHandler = sinon.stub();
+ completeHandler = sinon.stub();
+ queue = new uw.ConcurrentQueue( {
+ count: 2,
+ action: queueAction
+ } );
+
+ queue.connect( null, {
+ change: changeHandler,
+ progress: progressHandler,
+ complete: completeHandler
+ } );
+
+ queue.on( 'complete', function () {
+ sinon.assert.callCount( changeHandler, 6 );
+ sinon.assert.callCount( progressHandler, 6 );
+ sinon.assert.callCount( completeHandler, 1 );
+
+ assertCalledInOrder(
+ changeHandler, // Added 'a'
+ changeHandler, // Added 'b'
+ changeHandler, // Added 'c'
+ progressHandler, // Finished 'a' or 'b'
+ changeHandler, // Added 'd'
+ changeHandler, // Added 'e'
+ progressHandler, // Finished 'a', 'b' or 'c'
+ progressHandler, // Finished 'a', 'b', 'c' or 'd'
+ progressHandler, // Finished 'a', 'b', 'c', 'd' or 'e'
+ progressHandler, // Finished 'a', 'b', 'c', 'd' or 'e'
+ changeHandler, // Added 'f'
+ progressHandler, // Finished f'
+ completeHandler
+ );
+
+ done();
+ } );
+
+ queue.addItem( 'a' );
+ queue.addItem( 'b' );
+ queue.addItem( 'c' );
+ queue.once( 'progress', function () {
+ queue.addItem( 'd' );
+ queue.addItem( 'e' );
+ } );
+ queue.on( 'progress', function () {
+ if ( queue.done.length === 5 ) {
+ queue.addItem( 'f' );
+ }
+ } );
+ queue.startExecuting();
+ } );
+
+ QUnit.test( 'Deleting items while queue running', function ( assert ) {
+ var done, changeHandler, progressHandler, completeHandler, queue;
+ done = assert.async();
+ changeHandler = sinon.stub();
+ progressHandler = sinon.stub();
+ completeHandler = sinon.stub();
+ queue = new uw.ConcurrentQueue( {
+ count: 2,
+ action: queueAction
+ } );
+
+ queue.connect( null, {
+ change: changeHandler,
+ progress: progressHandler,
+ complete: completeHandler
+ } );
+
+ queue.on( 'complete', function () {
+ sinon.assert.callCount( changeHandler, 8 );
+ sinon.assert.callCount( progressHandler, 4 );
+ sinon.assert.callCount( completeHandler, 1 );
+
+ assertCalledInOrder(
+ changeHandler, // Added 'a'
+ changeHandler, // Added 'b'
+ changeHandler, // Added 'c'
+ changeHandler, // Added 'd'
+ changeHandler, // Added 'e'
+ changeHandler, // Added 'f'
+ progressHandler, // Finished 'a' or 'b'
+ changeHandler, // Removed first of the queued (not executing), which is 'd'
+ progressHandler, // Finished 'a', 'b' or 'c'
+ changeHandler, // Removed the last one queued (not executing), which is 'f'
+ progressHandler, // Finished 'a', 'b', 'c' or 'e'
+ progressHandler, // Finished 'a', 'b', 'c' or 'e'
+ completeHandler
+ );
+
+ done();
+ } );
+
+ queue.addItem( 'a' );
+ queue.addItem( 'b' );
+ queue.addItem( 'c' );
+ queue.addItem( 'd' );
+ queue.addItem( 'e' );
+ queue.addItem( 'f' );
+ queue.once( 'progress', function () {
+ queue.removeItem( queue.queued[ 0 ] );
+
+ queue.once( 'progress', function () {
+ queue.removeItem( queue.queued[ 0 ] );
+ } );
+ } );
+ queue.startExecuting();
+ } );
+
+ QUnit.test( 'Deleting currently running item', function ( assert ) {
+ var done, action, changeHandler, progressHandler, completeHandler, queue;
+ done = assert.async();
+ action = sinon.spy( queueAction );
+ changeHandler = sinon.stub();
+ progressHandler = sinon.stub();
+ completeHandler = sinon.stub();
+ queue = new uw.ConcurrentQueue( {
+ count: 2,
+ action: action
+ } );
+
+ queue.connect( null, {
+ change: changeHandler,
+ progress: progressHandler,
+ complete: completeHandler
+ } );
+
+ queue.on( 'complete', function () {
+ // Every item in the queue was executed...
+ sinon.assert.callCount( action, 4 );
+
+ sinon.assert.callCount( changeHandler, 5 );
+ // ...but the one we removed wasn't registered as finished
+ sinon.assert.callCount( progressHandler, 3 );
+ sinon.assert.callCount( completeHandler, 1 );
+
+ assertCalledInOrder(
+ changeHandler, // Added 'a'
+ changeHandler, // Added 'b'
+ changeHandler, // Added 'c'
+ changeHandler, // Added 'd'
+ action, // Started 'a'
+ action, // Started 'b'
+ progressHandler, // Finished 'a' or 'b'
+ changeHandler, // Removed first of the executing, which is 'a' or 'b'
+ action, // Started 'c'
+ action, // Started 'd' - note how two threads are running still
+ progressHandler, // Finished 'c' or 'd'
+ progressHandler, // Finished 'c' or 'd'
+ completeHandler
+ );
+
+ done();
+ } );
+
+ queue.addItem( 'a' );
+ queue.addItem( 'b' );
+ queue.addItem( 'c' );
+ queue.addItem( 'd' );
+ queue.once( 'progress', function () {
+ queue.removeItem( queue.running[ 0 ] );
+ } );
+ queue.startExecuting();
+ } );
+
+ QUnit.test( 'Adding a new item when almost done', function ( assert ) {
+ var done, action, changeHandler, progressHandler, completeHandler, queue, onProgress;
+ done = assert.async();
+ // This test seems extra flaky and was occasionally failing, double the delays
+ action = sinon.spy( queueAction );
+ changeHandler = sinon.stub();
+ progressHandler = sinon.stub();
+ completeHandler = sinon.stub();
+ queue = new uw.ConcurrentQueue( {
+ count: 2,
+ action: action
+ } );
+
+ queue.connect( null, {
+ change: changeHandler,
+ progress: progressHandler,
+ complete: completeHandler
+ } );
+
+ queue.on( 'complete', function () {
+ sinon.assert.callCount( action, 5 );
+ sinon.assert.callCount( changeHandler, 5 );
+ sinon.assert.callCount( progressHandler, 5 );
+ sinon.assert.callCount( completeHandler, 1 );
+
+ assertCalledInOrder(
+ changeHandler, // Added 'a'
+ changeHandler, // Added 'b'
+ changeHandler, // Added 'c'
+ changeHandler, // Added 'd'
+ action, // Started 'a'
+ action, // Started 'b'
+ progressHandler, // Finished 'a' or 'b'
+ action, // Started 'c'
+ progressHandler, // Finished 'a', 'b' or 'c'
+ action, // Started 'd'
+ progressHandler, // Finished 'a', 'b', 'c' or 'd'
+ changeHandler, // Added 'e'
+ action, // Started 'e' -- this starts a new thread
+ progressHandler, // Finished 'a', 'b', 'c', 'd' or 'e'
+ progressHandler, // Finished 'a', 'b', 'c', 'd' or 'e'
+ completeHandler
+ );
+
+ done();
+ } );
+
+ queue.addItem( 'a' );
+ queue.addItem( 'b' );
+ queue.addItem( 'c' );
+ queue.addItem( 'd' );
+ onProgress = function () {
+ if ( queue.done.length === 3 ) {
+ queue.addItem( 'e' );
+ queue.off( 'progress', onProgress );
+ }
+ };
+ queue.on( 'progress', onProgress );
+ queue.startExecuting();
+ } );
+
+}( mediaWiki.uploadWizard, jQuery ) );