UNPKG

kuyruk

Version:

Multifunctional Asynchronous Concurrent Queue

610 lines (484 loc) 11.8 kB
'use strict'; const { test, plan } = require('tap'); const { Kuyruk } = require('../kuyruk.js'); plan(24); const items = new Array(10).fill('test').map((e, i) => e + i); test('Done handling', (t) => { let doneCalled = false; const queue = new Kuyruk({ concurrency: 1 }) .process((item, callback) => { setTimeout(() => { callback(null, item); }, 0); }) .done((err, res) => { t.equal(err, null); t.equal(res, 'test'); doneCalled = true; }); t.plan(3); queue.add('test'); setTimeout(() => { t.equal(doneCalled, true); }, 0); }); test('Success handling', (t) => { let successCalled = false; const queue = new Kuyruk({ concurrency: 1 }) .process((item, callback) => { setTimeout(() => { callback(null, item); }, 0); }) .success((res) => { t.equal(res, 'test'); successCalled = true; }); t.plan(2); queue.add('test'); setTimeout(() => { t.equal(successCalled, true); }, 0); }); test('Error handling', (t) => { let failureCalled = false; const queue = new Kuyruk({ concurrency: 1 }) .process((item, callback) => { setTimeout(() => { callback(new Error('Task failed'), item); }, 0); }) .failure((err) => { t.equal(err.message, 'Task failed'); failureCalled = true; }); t.plan(2); queue.add('test'); setTimeout(() => { t.equal(failureCalled, true); }, 0); }); test('Timeout handling', (t) => { let onTimeoutCalled = false; let failureCalled = false; let timer = null; const queue = new Kuyruk({ concurrency: 1 }) .process((item, callback) => { timer = setTimeout(() => { t.fail('Never should this line'); callback(null, item); }, 100); }) .timeout(50, (err) => { t.equal(err.message, 'Process timed out!'); onTimeoutCalled = true; }) .failure((err) => { t.equal(err.message, 'Process timed out!'); failureCalled = true; }); t.plan(4); queue.add('test'); setTimeout(() => { t.equal(failureCalled, true); t.equal(onTimeoutCalled, true); clearTimeout(timer); }, 50); }); test('Wait handling', (t) => { let failureCalled = false; const queue = new Kuyruk({ concurrency: 1 }) .wait(25) .pause() .process((item, callback) => { setTimeout(() => { t.fail('Never should this line'); callback(null, item); }, 0); }) .failure((err) => { t.equal(err.message, 'Waiting timed out'); failureCalled = true; }); t.plan(3); queue.add('test'); setTimeout(() => { t.equal(failureCalled, false); queue.resume(); t.equal(failureCalled, true); }, 50); }); test('Pause handling', (t) => { let doneCalled = false; const queue = new Kuyruk({ concurrency: 1 }) .pause() .process((item, callback) => { setTimeout(() => { callback(null, item); }, 0); }) .done(() => { doneCalled = true; }); t.plan(2); t.equal(doneCalled, false); queue.add('test'); t.equal(doneCalled, false); }); test('Resume handling', (t) => { let doneCalled = false; const queue = new Kuyruk({ concurrency: 1 }) .pause() .process((item, callback) => { setTimeout(() => { callback(null, item); }, 0); }) .done(() => { doneCalled = true; }); t.plan(3); t.equal(doneCalled, false); queue.add('test'); t.equal(doneCalled, false); queue.resume(); setTimeout(() => { t.equal(doneCalled, true); }, 0); }); test('Drain handling', (t) => { let drainCalled = false; const queue = new Kuyruk({ concurrency: 1 }) .process((item, callback) => { setTimeout(() => { callback(null, item); }, 0); }) .drain(() => { drainCalled = true; }); t.plan(1); queue.add('test'); setTimeout(() => { t.equal(drainCalled, true); }, 0); }); test('Should task handling: promise', (t) => { const results = []; const queue = new Kuyruk({ concurrency: 1 }) .process( (item) => new Promise((resolve) => { setTimeout(() => { resolve(item); }, 0); }), ) .done((err, res) => { t.equal(err, null); t.ok(items.includes(res)); results.push(res); }) .drain(() => { t.equal(items.length, results.length); }); t.plan(2 * items.length + 1); for (const item of items) { queue.add(item); } }); test('Should task handling: callback', (t) => { const results = []; const queue = new Kuyruk({ concurrency: 1 }) .process((item, cb) => { setTimeout(() => { cb(null, item); }, 0); }) .done((err, res) => { t.equal(err, null); t.ok(items.includes(res)); results.push(res); }) .drain(() => { t.equal(items.length, results.length); }); t.plan(2 * items.length + 1); for (const item of items) { queue.add(item); } }); test('Should task handling: promise', (t) => { const results = []; const queue = new Kuyruk({ concurrency: 1 }) .done((err, res) => { t.equal(err, null); t.ok(items.includes(res)); results.push(res); }) .drain(() => { t.equal(items.length, results.length); }); t.plan(2 * items.length + 1); for (const item of items) { queue.add( () => new Promise((resolve) => { setTimeout(() => { resolve(item); }, 0); }), ); } }); test('Should task handling: multiple', (t) => { const results = []; const queue = new Kuyruk({ concurrency: 1 }) .process((item, callback) => { setTimeout(() => { callback(null, item); }, 0); }) .done((err, res) => { t.equal(err, null); t.ok(items.includes(res)); results.push(res); }) .drain(() => { t.equal(items.length * 2, results.length); }); t.plan(4 * items.length + 1); for (const item of items) { queue.add(item); queue.add( () => new Promise((resolve) => { setTimeout(() => { resolve(item); }, 0); }), ); } }); test('Concurrency handling', (t) => { const taskCount = 50; const concurrency = 5; const queue = new Kuyruk({ concurrency }) .process((item, callback) => { setTimeout(callback, 0, null, item); }) .done(() => { const channelsExceeded = queue.concurrency > concurrency; t.equal(channelsExceeded, false); }); t.plan(taskCount + 1); for (let i = 0; i <= taskCount; i++) { queue.add(`test${i}`); } }); test('Queue size handling', (t) => { const size = 10; const queue = new Kuyruk({ concurrency: 1, size }) .process((item, callback) => { setTimeout(callback, 100, null, item); }) .drain(() => { t.equal(queue.waiting.length, 0); t.equal(queue.size, size); t.equal(queue.count, 0); }); t.plan(5); t.equal(queue.size, size); for (let i = 0; i < 20; i++) { queue.add(`test${i}`); } t.equal(queue.waiting.length, size); }); test('Should add task without process', (t) => { const queue = new Kuyruk({ concurrency: 1 }).done(() => { t.fail('Never should this line'); }); t.plan(1); try { queue.add('test'); t.fail('Never should this line'); } catch (err) { t.equal(err.message, 'Process is not defined'); } }); test('Queue pipe handling', (t) => { const dest1 = new Kuyruk({ concurrency: 1 }) .process((item, callback) => { setTimeout(() => { callback(null, item); }, 0); }) .done((err, res) => { t.equal(err, null); t.ok(items.includes(res)); }); const dest2 = new Kuyruk({ concurrency: 1 }) .process((item, callback) => { setTimeout(() => { callback(null, item); }, 0); }) .done((err, res) => { t.equal(err, null); t.ok(items.includes(res)); }); const queue = new Kuyruk({ concurrency: 1 }) .process((item, callback) => { setTimeout(() => { callback(null, item); }, 0); }) .done((err, res) => { t.equal(err, null); t.ok(items.includes(res)); }); t.plan(6 * items.length + 3); queue.pipe(dest1).pipe(dest2); t.equal(queue.destination, dest1); t.equal(dest1.destination, dest2); t.equal(dest2.destination, null); for (const item of items) { queue.add(item); } }); test('Should queue clear', (t) => { const queue = new Kuyruk({ concurrency: 1 }).pause().done(() => { t.fail('Never should this line'); }); const dest = new Kuyruk({ concurrency: 1 }).done(() => { t.fail('Never should this line'); }); queue.pipe(dest); t.plan(5); queue.add('test1'); queue.add('test2'); queue.add('test3'); t.equal(queue.waiting.length, 3); t.equal(queue.destination, dest); queue.clear().resume(); t.equal(queue.waiting.length, 0); t.equal(queue.count, 0); t.equal(queue.destination, null); }); test('Fifo', (t) => { let i = 0; const queue = new Kuyruk({ concurrency: 1 }) .process((item, callback) => { setTimeout(() => { callback(null, item); }, 0); }) .success((res) => { t.equal(items[i++], res); }); t.plan(items.length); for (const item of items) { queue.add(item); } }); test('Lifo', (t) => { let i = items.length - 1; const queue = new Kuyruk({ concurrency: 1 }) .lifo() .pause() .process((item, callback) => { setTimeout(() => { callback(null, item); }, 0); }) .success((res) => { t.equal(items[i--], res); }); t.plan(items.length); for (const item of items) { queue.add(item); } queue.resume(); }); test('Priority', (t) => { const cases = [ 'test0', 'test1', 'test2', 'test9', 'test8', 'test7', 'test6', 'test5', 'test4', 'test3', ]; let i = 0; const queue = new Kuyruk({ concurrency: 3 }) .priority() .process((item, callback) => { setTimeout(() => { callback(null, item); }, 0); }) .success((res) => { t.equal(res, cases[i++]); }); t.plan(cases.length); const max = cases.length; for (let i = 0; i < max; i++) { const item = cases[i]; const priority = max - i; queue.add(item, { priority }); } }); test('Details (positive factor)', (t) => { t.plan(4); const queue = new Kuyruk({ concurrency: 3 }) .setFactor(3) .process(() => Promise.resolve('test')) .success((res, details) => { t.equal(res, 'test'); t.ok(typeof details === 'object'); t.ok(details.hasOwnProperty('factor')); t.equal(details.factor, 3); }); queue.add('test'); }); test('Details with (negative factor)', (t) => { t.plan(4); const queue = new Kuyruk({ concurrency: 3 }) .setFactor(0) .process(() => Promise.resolve('test')) .success((res, details) => { t.equal(res, 'test'); t.ok(typeof details === 'object'); t.ok(details.hasOwnProperty('factor')); t.equal(details.factor, 0); }); queue.add('test'); }); test('Queue empty method handling', (t) => { t.plan(3); const queue = new Kuyruk({ concurrency: 1 }); t.equal(queue.isEmpty(), true); queue .process(() => Promise.resolve('test')) .drain(() => { t.equal(queue.isEmpty(), true); }) .add('test'); t.equal(queue.isEmpty(), false); }); test('Add return handling (boolean)', (t) => { t.plan(2); const queue = new Kuyruk({ concurrency: 1, size: 1 }).pause(); t.equal( queue.add(async () => {}), true, ); t.equal( queue.add(async () => {}), false, ); });