You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					490 lines
				
				11 KiB
			
		
		
			
		
	
	
					490 lines
				
				11 KiB
			| 
											3 years ago
										 | "use strict"; | ||
|  | 
 | ||
|  | const promisify = require("util.promisify"); | ||
|  | const gensync = require("../"); | ||
|  | 
 | ||
|  | const TEST_ERROR = new Error("TEST_ERROR"); | ||
|  | 
 | ||
|  | const DID_ERROR = new Error("DID_ERROR"); | ||
|  | 
 | ||
|  | const doSuccess = gensync({ | ||
|  |   sync: () => 42, | ||
|  |   async: () => Promise.resolve(42), | ||
|  | }); | ||
|  | 
 | ||
|  | const doError = gensync({ | ||
|  |   sync: () => { | ||
|  |     throw DID_ERROR; | ||
|  |   }, | ||
|  |   async: () => Promise.reject(DID_ERROR), | ||
|  | }); | ||
|  | 
 | ||
|  | function throwTestError() { | ||
|  |   throw TEST_ERROR; | ||
|  | } | ||
|  | 
 | ||
|  | async function expectResult( | ||
|  |   fn, | ||
|  |   arg, | ||
|  |   { error, value, expectSync = false, syncErrback = expectSync } | ||
|  | ) { | ||
|  |   if (!expectSync) { | ||
|  |     expect(() => fn.sync(arg)).toThrow(TEST_ERROR); | ||
|  |   } else if (error) { | ||
|  |     expect(() => fn.sync(arg)).toThrow(error); | ||
|  |   } else { | ||
|  |     expect(fn.sync(arg)).toBe(value); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (error) { | ||
|  |     await expect(fn.async(arg)).rejects.toBe(error); | ||
|  |   } else { | ||
|  |     await expect(fn.async(arg)).resolves.toBe(value); | ||
|  |   } | ||
|  | 
 | ||
|  |   await new Promise((resolve, reject) => { | ||
|  |     let sync = true; | ||
|  |     fn.errback(arg, (err, val) => { | ||
|  |       try { | ||
|  |         expect(err).toBe(error); | ||
|  |         expect(val).toBe(value); | ||
|  |         expect(sync).toBe(syncErrback); | ||
|  | 
 | ||
|  |         resolve(); | ||
|  |       } catch (e) { | ||
|  |         reject(e); | ||
|  |       } | ||
|  |     }); | ||
|  |     sync = false; | ||
|  |   }); | ||
|  | } | ||
|  | 
 | ||
|  | describe("gensync({})", () => { | ||
|  |   describe("option validation", () => { | ||
|  |     test("disallow async and errback handler together", () => { | ||
|  |       try { | ||
|  |         gensync({ | ||
|  |           sync: throwTestError, | ||
|  |           async: throwTestError, | ||
|  |           errback: throwTestError, | ||
|  |         }); | ||
|  | 
 | ||
|  |         throwTestError(); | ||
|  |       } catch (err) { | ||
|  |         expect(err.message).toMatch( | ||
|  |           /Expected one of either opts.async or opts.errback, but got _both_\./ | ||
|  |         ); | ||
|  |         expect(err.code).toBe("GENSYNC_OPTIONS_ERROR"); | ||
|  |       } | ||
|  |     }); | ||
|  | 
 | ||
|  |     test("disallow missing sync handler", () => { | ||
|  |       try { | ||
|  |         gensync({ | ||
|  |           async: throwTestError, | ||
|  |         }); | ||
|  | 
 | ||
|  |         throwTestError(); | ||
|  |       } catch (err) { | ||
|  |         expect(err.message).toMatch(/Expected opts.sync to be a function./); | ||
|  |         expect(err.code).toBe("GENSYNC_OPTIONS_ERROR"); | ||
|  |       } | ||
|  |     }); | ||
|  | 
 | ||
|  |     test("errback callback required", () => { | ||
|  |       const fn = gensync({ | ||
|  |         sync: throwTestError, | ||
|  |         async: throwTestError, | ||
|  |       }); | ||
|  | 
 | ||
|  |       try { | ||
|  |         fn.errback(); | ||
|  | 
 | ||
|  |         throwTestError(); | ||
|  |       } catch (err) { | ||
|  |         expect(err.message).toMatch(/function called without callback/); | ||
|  |         expect(err.code).toBe("GENSYNC_ERRBACK_NO_CALLBACK"); | ||
|  |       } | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe("generator function metadata", () => { | ||
|  |     test("automatic naming", () => { | ||
|  |       expect( | ||
|  |         gensync({ | ||
|  |           sync: function readFileSync() {}, | ||
|  |           async: () => {}, | ||
|  |         }).name | ||
|  |       ).toBe("readFile"); | ||
|  |       expect( | ||
|  |         gensync({ | ||
|  |           sync: function readFile() {}, | ||
|  |           async: () => {}, | ||
|  |         }).name | ||
|  |       ).toBe("readFile"); | ||
|  |       expect( | ||
|  |         gensync({ | ||
|  |           sync: function readFileAsync() {}, | ||
|  |           async: () => {}, | ||
|  |         }).name | ||
|  |       ).toBe("readFileAsync"); | ||
|  | 
 | ||
|  |       expect( | ||
|  |         gensync({ | ||
|  |           sync: () => {}, | ||
|  |           async: function readFileSync() {}, | ||
|  |         }).name | ||
|  |       ).toBe("readFileSync"); | ||
|  |       expect( | ||
|  |         gensync({ | ||
|  |           sync: () => {}, | ||
|  |           async: function readFile() {}, | ||
|  |         }).name | ||
|  |       ).toBe("readFile"); | ||
|  |       expect( | ||
|  |         gensync({ | ||
|  |           sync: () => {}, | ||
|  |           async: function readFileAsync() {}, | ||
|  |         }).name | ||
|  |       ).toBe("readFile"); | ||
|  | 
 | ||
|  |       expect( | ||
|  |         gensync({ | ||
|  |           sync: () => {}, | ||
|  |           errback: function readFileSync() {}, | ||
|  |         }).name | ||
|  |       ).toBe("readFileSync"); | ||
|  |       expect( | ||
|  |         gensync({ | ||
|  |           sync: () => {}, | ||
|  |           errback: function readFile() {}, | ||
|  |         }).name | ||
|  |       ).toBe("readFile"); | ||
|  |       expect( | ||
|  |         gensync({ | ||
|  |           sync: () => {}, | ||
|  |           errback: function readFileAsync() {}, | ||
|  |         }).name | ||
|  |       ).toBe("readFileAsync"); | ||
|  |     }); | ||
|  | 
 | ||
|  |     test("explicit naming", () => { | ||
|  |       expect( | ||
|  |         gensync({ | ||
|  |           name: "readFile", | ||
|  |           sync: () => {}, | ||
|  |           async: () => {}, | ||
|  |         }).name | ||
|  |       ).toBe("readFile"); | ||
|  |     }); | ||
|  | 
 | ||
|  |     test("default arity", () => { | ||
|  |       expect( | ||
|  |         gensync({ | ||
|  |           sync: function(a, b, c, d, e, f, g) { | ||
|  |             throwTestError(); | ||
|  |           }, | ||
|  |           async: throwTestError, | ||
|  |         }).length | ||
|  |       ).toBe(7); | ||
|  |     }); | ||
|  | 
 | ||
|  |     test("explicit arity", () => { | ||
|  |       expect( | ||
|  |         gensync({ | ||
|  |           arity: 3, | ||
|  |           sync: throwTestError, | ||
|  |           async: throwTestError, | ||
|  |         }).length | ||
|  |       ).toBe(3); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe("'sync' handler", async () => { | ||
|  |     test("success", async () => { | ||
|  |       const fn = gensync({ | ||
|  |         sync: (...args) => JSON.stringify(args), | ||
|  |       }); | ||
|  | 
 | ||
|  |       await expectResult(fn, 42, { value: "[42]", expectSync: true }); | ||
|  |     }); | ||
|  | 
 | ||
|  |     test("failure", async () => { | ||
|  |       const fn = gensync({ | ||
|  |         sync: (...args) => { | ||
|  |           throw JSON.stringify(args); | ||
|  |         }, | ||
|  |       }); | ||
|  | 
 | ||
|  |       await expectResult(fn, 42, { error: "[42]", expectSync: true }); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe("'async' handler", async () => { | ||
|  |     test("success", async () => { | ||
|  |       const fn = gensync({ | ||
|  |         sync: throwTestError, | ||
|  |         async: (...args) => Promise.resolve(JSON.stringify(args)), | ||
|  |       }); | ||
|  | 
 | ||
|  |       await expectResult(fn, 42, { value: "[42]" }); | ||
|  |     }); | ||
|  | 
 | ||
|  |     test("failure", async () => { | ||
|  |       const fn = gensync({ | ||
|  |         sync: throwTestError, | ||
|  |         async: (...args) => Promise.reject(JSON.stringify(args)), | ||
|  |       }); | ||
|  | 
 | ||
|  |       await expectResult(fn, 42, { error: "[42]" }); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe("'errback' sync handler", async () => { | ||
|  |     test("success", async () => { | ||
|  |       const fn = gensync({ | ||
|  |         sync: throwTestError, | ||
|  |         errback: (...args) => args.pop()(null, JSON.stringify(args)), | ||
|  |       }); | ||
|  | 
 | ||
|  |       await expectResult(fn, 42, { value: "[42]", syncErrback: true }); | ||
|  |     }); | ||
|  | 
 | ||
|  |     test("failure", async () => { | ||
|  |       const fn = gensync({ | ||
|  |         sync: throwTestError, | ||
|  |         errback: (...args) => args.pop()(JSON.stringify(args)), | ||
|  |       }); | ||
|  | 
 | ||
|  |       await expectResult(fn, 42, { error: "[42]", syncErrback: true }); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe("'errback' async handler", async () => { | ||
|  |     test("success", async () => { | ||
|  |       const fn = gensync({ | ||
|  |         sync: throwTestError, | ||
|  |         errback: (...args) => | ||
|  |           process.nextTick(() => args.pop()(null, JSON.stringify(args))), | ||
|  |       }); | ||
|  | 
 | ||
|  |       await expectResult(fn, 42, { value: "[42]" }); | ||
|  |     }); | ||
|  | 
 | ||
|  |     test("failure", async () => { | ||
|  |       const fn = gensync({ | ||
|  |         sync: throwTestError, | ||
|  |         errback: (...args) => | ||
|  |           process.nextTick(() => args.pop()(JSON.stringify(args))), | ||
|  |       }); | ||
|  | 
 | ||
|  |       await expectResult(fn, 42, { error: "[42]" }); | ||
|  |     }); | ||
|  |   }); | ||
|  | }); | ||
|  | 
 | ||
|  | describe("gensync(function* () {})", () => { | ||
|  |   test("sync throw before body", async () => { | ||
|  |     const fn = gensync(function*(arg = throwTestError()) {}); | ||
|  | 
 | ||
|  |     await expectResult(fn, undefined, { | ||
|  |       error: TEST_ERROR, | ||
|  |       syncErrback: true, | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   test("sync throw inside body", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       throwTestError(); | ||
|  |     }); | ||
|  | 
 | ||
|  |     await expectResult(fn, undefined, { | ||
|  |       error: TEST_ERROR, | ||
|  |       syncErrback: true, | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   test("async throw inside body", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       const val = yield* doSuccess(); | ||
|  |       throwTestError(); | ||
|  |     }); | ||
|  | 
 | ||
|  |     await expectResult(fn, undefined, { | ||
|  |       error: TEST_ERROR, | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   test("error inside body", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       yield* doError(); | ||
|  |     }); | ||
|  | 
 | ||
|  |     await expectResult(fn, undefined, { | ||
|  |       error: DID_ERROR, | ||
|  |       expectSync: true, | ||
|  |       syncErrback: false, | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   test("successful return value", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       const value = yield* doSuccess(); | ||
|  | 
 | ||
|  |       expect(value).toBe(42); | ||
|  | 
 | ||
|  |       return 84; | ||
|  |     }); | ||
|  | 
 | ||
|  |     await expectResult(fn, undefined, { | ||
|  |       value: 84, | ||
|  |       expectSync: true, | ||
|  |       syncErrback: false, | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   test("successful final value", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       return 42; | ||
|  |     }); | ||
|  | 
 | ||
|  |     await expectResult(fn, undefined, { | ||
|  |       value: 42, | ||
|  |       expectSync: true, | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   test("yield unexpected object", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       yield {}; | ||
|  |     }); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await fn.async(); | ||
|  | 
 | ||
|  |       throwTestError(); | ||
|  |     } catch (err) { | ||
|  |       expect(err.message).toMatch( | ||
|  |         /Got unexpected yielded value in gensync generator/ | ||
|  |       ); | ||
|  |       expect(err.code).toBe("GENSYNC_EXPECTED_START"); | ||
|  |     } | ||
|  |   }); | ||
|  | 
 | ||
|  |   test("yield suspend yield", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       yield Symbol.for("gensync:v1:start"); | ||
|  | 
 | ||
|  |       // Should be "yield*" for no error.
 | ||
|  |       yield {}; | ||
|  |     }); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await fn.async(); | ||
|  | 
 | ||
|  |       throwTestError(); | ||
|  |     } catch (err) { | ||
|  |       expect(err.message).toMatch(/Expected GENSYNC_SUSPEND, got {}/); | ||
|  |       expect(err.code).toBe("GENSYNC_EXPECTED_SUSPEND"); | ||
|  |     } | ||
|  |   }); | ||
|  | 
 | ||
|  |   test("yield suspend return", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       yield Symbol.for("gensync:v1:start"); | ||
|  | 
 | ||
|  |       // Should be "yield*" for no error.
 | ||
|  |       return {}; | ||
|  |     }); | ||
|  | 
 | ||
|  |     try { | ||
|  |       await fn.async(); | ||
|  | 
 | ||
|  |       throwTestError(); | ||
|  |     } catch (err) { | ||
|  |       expect(err.message).toMatch(/Unexpected generator completion/); | ||
|  |       expect(err.code).toBe("GENSYNC_EXPECTED_SUSPEND"); | ||
|  |     } | ||
|  |   }); | ||
|  | }); | ||
|  | 
 | ||
|  | describe("gensync.all()", () => { | ||
|  |   test("success", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       const result = yield* gensync.all([doSuccess(), doSuccess()]); | ||
|  | 
 | ||
|  |       expect(result).toEqual([42, 42]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     await expectResult(fn, undefined, { | ||
|  |       value: undefined, | ||
|  |       expectSync: true, | ||
|  |       syncErrback: false, | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   test("error first", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       yield* gensync.all([doError(), doSuccess()]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     await expectResult(fn, undefined, { | ||
|  |       error: DID_ERROR, | ||
|  |       expectSync: true, | ||
|  |       syncErrback: false, | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   test("error last", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       yield* gensync.all([doSuccess(), doError()]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     await expectResult(fn, undefined, { | ||
|  |       error: DID_ERROR, | ||
|  |       expectSync: true, | ||
|  |       syncErrback: false, | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   test("empty list", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       yield* gensync.all([]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     await expectResult(fn, undefined, { | ||
|  |       value: undefined, | ||
|  |       expectSync: true, | ||
|  |       syncErrback: false, | ||
|  |     }); | ||
|  |   }); | ||
|  | }); | ||
|  | 
 | ||
|  | describe("gensync.race()", () => { | ||
|  |   test("success", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       const result = yield* gensync.race([doSuccess(), doError()]); | ||
|  | 
 | ||
|  |       expect(result).toEqual(42); | ||
|  |     }); | ||
|  | 
 | ||
|  |     await expectResult(fn, undefined, { | ||
|  |       value: undefined, | ||
|  |       expectSync: true, | ||
|  |       syncErrback: false, | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   test("error", async () => { | ||
|  |     const fn = gensync(function*() { | ||
|  |       yield* gensync.race([doError(), doSuccess()]); | ||
|  |     }); | ||
|  | 
 | ||
|  |     await expectResult(fn, undefined, { | ||
|  |       error: DID_ERROR, | ||
|  |       expectSync: true, | ||
|  |       syncErrback: false, | ||
|  |     }); | ||
|  |   }); | ||
|  | }); |