synchronize
Version:
Turns asynchronous function into synchronous
429 lines (383 loc) • 10.9 kB
JavaScript
/*jshint node: true, indent:2, loopfunc: true, asi: true, undef:true, mocha: true */
// require('longjohn')
var sync = require('../sync')
var Fiber = require('fibers')
var expect = require('chai').expect
describe('Control Flow', function(){
var waitAndReturn = function(time, err, value, cb){
setTimeout(function(){cb(err, value)}, time)
}
var fn = function(arg, cb){
expect(arg).to.eql('something')
process.nextTick(function(){
cb(null, 'ok')
})
}
it('should provide await & defer', function(done){
sync.fiber(function(){
var result = sync.await(fn('something', sync.defer()))
expect(result).to.eql('ok')
}, done)
})
it('should synchronize function', function(done){
fn = sync(fn)
sync.fiber(function(){
expect(fn('something')).to.eql('ok')
}, done)
})
it('should be save aginst synchronizing function twice', function(done){
fn = sync(sync(fn))
sync.fiber(function(){
expect(fn('something')).to.eql('ok')
}, done)
})
it('should allow call synchronized function explicitly', function(done){
fn = sync(fn)
fn('something', function(err, result){
expect(result).to.eql('ok')
done(err)
})
})
it("should catch asynchronous errors", function(done){
var fn = function(cb){
process.nextTick(function(){
cb(new Error('an error'))
})
}
fn = sync(fn)
sync.fiber(function(){
var err
try {
fn()
} catch (e) {
err = e
}
expect(err.message).to.eql('an error')
}, done)
})
it("should be compatible with not asynchronous cbs", function(done){
fn = function(cb){
cb(null, 'ok')
}
fn = sync(fn)
sync.fiber(function(){
expect(fn()).to.eql('ok')
}, done)
})
it("should catch non asynchronous errors", function(done){
fn = function(cb){
cb(new Error('an error'))
}
fn = sync(fn)
sync.fiber(function(){
var err
try {
fn()
} catch (e) {
err = e
}
expect(err.message).to.eql('an error')
}, done)
})
describe("Special cases", function(){
it('should be able to emulate sleep', function(done){
var sleep = function(ms){
sync.await(setTimeout(sync.defer(), ms))
}
sync.fiber(function(){
var start = new Date().getTime()
sleep(50)
expect(new Date().getTime()).to.be.greaterThan(start)
}, done)
})
})
it('should support parallel calls', function(done){
sync.fiber(function(){
var calls = []
var readA = function(cb){
calls.push('readA')
process.nextTick(function(){
calls.push('nextTick')
cb(null, 'dataA')
})
}
var readB = function(cb){
calls.push('readB')
process.nextTick(function(){
calls.push('nextTick')
cb(null, 'dataB')
})
}
sync.parallel(function(){
readA(sync.defer())
readB(sync.defer())
})
var results = sync.await()
expect(results).to.eql(['dataA', 'dataB'])
expect(calls).to.eql(['readA', 'readB', 'nextTick', 'nextTick'])
}, done)
})
it('should support multiple arguments', function(done){
sync.fiber(function(){
var read = function(cb){
process.nextTick(function(){
cb(null, 'data1', 'data2')
})
}
var result = sync.await(read(sync.defers()))
expect(result).to.eql(['data1', 'data2'])
}, done)
})
it('should support multiple `named` arguments', function(done){
sync.fiber(function(){
var read = function(cb){
process.nextTick(function(){
cb(null, 'data1', 'data2')
})
}
var result = sync.await(read(sync.defers('a', 'b')))
expect(result).to.eql({a: 'data1', b: 'data2'})
}, done)
})
it('should support multiple arguments parallel calls', function(done){
sync.fiber(function(){
var read = function(cb){
process.nextTick(function(){
cb(null, 'data1', 'data2')
})
}
sync.parallel(function(){
read(sync.defers())
read(sync.defers('a', 'b'))
})
var results = sync.await()
expect(results).to.eql([['data1', 'data2'], {a: 'data1', b: 'data2'}])
}, done)
})
it('should fiber call just once at raise error', function(done){
var read = function(cb){
process.nextTick(function(){
cb(new Error('an error'))
})
}
var called = 0
sync.fiber(function(){
called += 1
sync.await(read(sync.defers()))
}, function() {})
setTimeout(function(){
expect(called).to.eql(1)
done()
}, 100)
})
it('should handle parallel errors', function(done){
sync.fiber(function(){
var calls = []
var readA = function(cb){
process.nextTick(function(){
cb(new Error("error a"))
})
}
var readB = function(cb){
process.nextTick(function(){
cb(new Error("error b"))
})
}
try {
sync.parallel(function(){
readA(sync.defer())
readB(sync.defer())
})
var results = sync.await()
}catch(err){
if(err.message == 'error a') expect(err.message).to.eql('error a')
else expect(err.message).to.eql('error b')
}
}, done)
})
it('should handle parallel errors with multiple arguments', function(done){
sync.fiber(function(){
var calls = []
var readA = function(cb){
process.nextTick(function(){
cb(new Error("error a"))
})
}
var readB = function(cb){
process.nextTick(function(){
cb(new Error("error b"))
})
}
try {
sync.parallel(function(){
readA(sync.defers())
readB(sync.defers())
})
var results = sync.await()
}catch(err){
if(err.message == 'error a') expect(err.message).to.eql('error a')
else expect(err.message).to.eql('error b')
}
}, done)
})
it('should not unwind when await is called after an empty parallel block', function(done){
sync.fiber(function(){
sync.parallel(function(){
// Imagine that the user intends to enumerate an array here, calling
// `defer` once per array item, but the array is empty.
})
expect(sync.await()).to.eql([])
}, done)
})
it('should return result from fiber', function(done){
sync.fiber(function(){
return 'some value'
}, function(err, result){
expect(err).to.eql(null)
expect(result).to.eql('some value')
done()
})
})
it('should abort on timeout', function(done){
sync.fiber(function(){
waitAndReturn(10, null, 'some result', sync.deferWithTimeout(100))
expect(sync.await()).to.eql('some result')
try{
waitAndReturn(10, null, 'some result', sync.deferWithTimeout(1))
expect(sync.await()).to.eql('some result')
}catch(err){
expect(err.message).to.eql('defer timed out!')
}
}, done)
})
// TODO, add also the same specs for `defers`.
it('should not resume terminated fiber with value', function(done){
var runCount = 0
var results = []
sync.fiber(function(){
runCount += 1
waitAndReturn(1, null, 'some value', sync.defer())
}, function(err){
results.push(err || null)
})
// Need to wait for some time after the fiber ends its execution to make sure
// it won't be runned one more time.
setTimeout(function(){
expect(runCount).to.eql(1)
expect(results.length).to.eql(1)
expect(results[0]).to.eql(null)
done()
}, 10)
})
// TODO, add also the same specs for `defers`.
it('should not resume terminated fiber with error', function(done){
var runCount = 0
var results = []
sync.fiber(function(){
runCount += 1
waitAndReturn(1, (new Error('some error')), null, sync.defer())
}, function(err){
results.push(err)
})
// Need to wait for some time after the fiber ends its execution to make sure
// it won't be runned one more time.
setTimeout(function(){
expect(runCount).to.eql(1)
expect(results.length).to.eql(1)
expect(results[0]).to.eql(null)
done()
}, 10)
})
it('should call terminate callback just once', function(done) {
var callCount = 0
var callback = function(error) {
callCount += 1
throw new Error('some error')
}
expect(function() {sync.fiber(function() {}, callback)}).to.throw(Error)
setTimeout(function() {
expect(callCount).to.eql(1)
done()
}, 10)
})
it('should throw error if defer called twice', function(done){
sync.fiber(function(){
var defer = sync.defer()
defer()
sync.await()
expect(defer).to.throw(Error)
}, done)
})
it('should call defer only once in the fiber process', function(done){
var broken = function(cb) {
sync.fiber(function() {
cb()
}, cb)
}
sync.fiber(function(){
broken(sync.defer())
sync.await()
throw new Error('an error')
}, function(err) {
expect(err).to.exist
expect(err.message).to.eql("defer can't be used twice!")
done()
})
})
it('should throw error if defers called twice', function(done){
sync.fiber(function(){
var defer = sync.defers()
defer()
sync.await()
expect(defer).to.throw(Error)
}, done)
})
it('should prevent defers call just once in fiber process', function(done){
var broken = function(cb) {
sync.fiber(function() {
cb()
}, cb)
}
sync.fiber(function(){
broken(sync.defers())
sync.await()
throw new Error('an error')
}, function(err) {
expect(err).to.exist
expect(err.message).to.eql("defer can't be used twice!")
done()
})
})
it('should prevent restart fiber', function(done){
var currentFiber
var called = 0
sync.fiber(function(){
called += 1
currentFiber = sync.Fiber.current
})
setTimeout(function() {
currentFiber.run()
}, 1)
setTimeout(function() {
expect(called).to.eql(1)
done()
}, 10);
})
it('should throw error when not matched defer-await pair', function(done){
sync.fiber(function(){
process.nextTick(sync.defer());
expect(function() { process.nextTick(sync.defer()) }).to.throw(Error)
sync.await()
process.nextTick(sync.defers());
expect(function() { process.nextTick(sync.defers()) }).to.throw(Error)
sync.await()
}, done)
})
beforeEach(function(){
this.someKey = 'some value'
})
it('should provide asyncIt helper for tests', sync.asyncIt(function(){
expect(Fiber.current).to.exist
expect(this.someKey).to.eql('some value')
}))
})