Ohm-Management - Projektarbeit B-ME
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.

function_call.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. /*
  2. * Copyright (c) 2012 Mathieu Turcotte
  3. * Licensed under the MIT license.
  4. */
  5. var assert = require('assert');
  6. var events = require('events');
  7. var sinon = require('sinon');
  8. var util = require('util');
  9. var FunctionCall = require('../lib/function_call');
  10. function MockBackoff() {
  11. events.EventEmitter.call(this);
  12. this.reset = sinon.spy();
  13. this.backoff = sinon.spy();
  14. this.failAfter = sinon.spy();
  15. }
  16. util.inherits(MockBackoff, events.EventEmitter);
  17. exports["FunctionCall"] = {
  18. setUp: function(callback) {
  19. this.wrappedFn = sinon.stub();
  20. this.callback = sinon.stub();
  21. this.backoff = new MockBackoff();
  22. this.backoffFactory = sinon.stub();
  23. this.backoffFactory.returns(this.backoff);
  24. callback();
  25. },
  26. tearDown: function(callback) {
  27. callback();
  28. },
  29. "constructor's first argument should be a function": function(test) {
  30. test.throws(function() {
  31. new FunctionCall(1, [], function() {});
  32. }, /Expected fn to be a function./);
  33. test.done();
  34. },
  35. "constructor's last argument should be a function": function(test) {
  36. test.throws(function() {
  37. new FunctionCall(function() {}, [], 3);
  38. }, /Expected callback to be a function./);
  39. test.done();
  40. },
  41. "isPending should return false once the call is started": function(test) {
  42. this.wrappedFn.
  43. onFirstCall().yields(new Error()).
  44. onSecondCall().yields(null, 'Success!');
  45. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  46. test.ok(call.isPending());
  47. call.start(this.backoffFactory);
  48. test.ok(!call.isPending());
  49. this.backoff.emit('ready');
  50. test.ok(!call.isPending());
  51. test.done();
  52. },
  53. "isRunning should return true when call is in progress": function(test) {
  54. this.wrappedFn.
  55. onFirstCall().yields(new Error()).
  56. onSecondCall().yields(null, 'Success!');
  57. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  58. test.ok(!call.isRunning());
  59. call.start(this.backoffFactory);
  60. test.ok(call.isRunning());
  61. this.backoff.emit('ready');
  62. test.ok(!call.isRunning());
  63. test.done();
  64. },
  65. "isCompleted should return true once the call completes": function(test) {
  66. this.wrappedFn.
  67. onFirstCall().yields(new Error()).
  68. onSecondCall().yields(null, 'Success!');
  69. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  70. test.ok(!call.isCompleted());
  71. call.start(this.backoffFactory);
  72. test.ok(!call.isCompleted());
  73. this.backoff.emit('ready');
  74. test.ok(call.isCompleted());
  75. test.done();
  76. },
  77. "isAborted should return true once the call is aborted": function(test) {
  78. this.wrappedFn.
  79. onFirstCall().yields(new Error()).
  80. onSecondCall().yields(null, 'Success!');
  81. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  82. test.ok(!call.isAborted());
  83. call.abort();
  84. test.ok(call.isAborted());
  85. test.done();
  86. },
  87. "setStrategy should overwrite the default strategy": function(test) {
  88. var replacementStrategy = {};
  89. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  90. call.setStrategy(replacementStrategy);
  91. call.start(this.backoffFactory);
  92. test.ok(this.backoffFactory.calledWith(replacementStrategy),
  93. 'User defined strategy should be used to instantiate ' +
  94. 'the backoff instance.');
  95. test.done();
  96. },
  97. "setStrategy should throw if the call is in progress": function(test) {
  98. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  99. call.start(this.backoffFactory);
  100. test.throws(function() {
  101. call.setStrategy({});
  102. }, /in progress/);
  103. test.done();
  104. },
  105. "failAfter should not be set by default": function(test) {
  106. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  107. call.start(this.backoffFactory);
  108. test.equal(0, this.backoff.failAfter.callCount);
  109. test.done();
  110. },
  111. "failAfter should be used as the maximum number of backoffs": function(test) {
  112. var failAfterValue = 99;
  113. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  114. call.failAfter(failAfterValue);
  115. call.start(this.backoffFactory);
  116. test.ok(this.backoff.failAfter.calledWith(failAfterValue),
  117. 'User defined maximum number of backoffs shoud be ' +
  118. 'used to configure the backoff instance.');
  119. test.done();
  120. },
  121. "failAfter should throw if the call is in progress": function(test) {
  122. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  123. call.start(this.backoffFactory);
  124. test.throws(function() {
  125. call.failAfter(1234);
  126. }, /in progress/);
  127. test.done();
  128. },
  129. "start shouldn't allow overlapping invocation": function(test) {
  130. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  131. var backoffFactory = this.backoffFactory;
  132. call.start(backoffFactory);
  133. test.throws(function() {
  134. call.start(backoffFactory);
  135. }, /already started/);
  136. test.done();
  137. },
  138. "start shouldn't allow invocation of aborted call": function(test) {
  139. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  140. var backoffFactory = this.backoffFactory;
  141. call.abort();
  142. test.throws(function() {
  143. call.start(backoffFactory);
  144. }, /aborted/);
  145. test.done();
  146. },
  147. "call should forward its arguments to the wrapped function": function(test) {
  148. var call = new FunctionCall(this.wrappedFn, [1, 2, 3], this.callback);
  149. call.start(this.backoffFactory);
  150. test.ok(this.wrappedFn.calledWith(1, 2, 3));
  151. test.done();
  152. },
  153. "call should complete when the wrapped function succeeds": function(test) {
  154. var call = new FunctionCall(this.wrappedFn, [1, 2, 3], this.callback);
  155. this.wrappedFn.
  156. onCall(0).yields(new Error()).
  157. onCall(1).yields(new Error()).
  158. onCall(2).yields(new Error()).
  159. onCall(3).yields(null, 'Success!');
  160. call.start(this.backoffFactory);
  161. for (var i = 0; i < 2; i++) {
  162. this.backoff.emit('ready');
  163. }
  164. test.equals(this.callback.callCount, 0);
  165. this.backoff.emit('ready');
  166. test.ok(this.callback.calledWith(null, 'Success!'));
  167. test.ok(this.wrappedFn.alwaysCalledWith(1, 2, 3));
  168. test.done();
  169. },
  170. "call should fail when the backoff limit is reached": function(test) {
  171. var call = new FunctionCall(this.wrappedFn, [1, 2, 3], this.callback);
  172. var error = new Error();
  173. this.wrappedFn.yields(error);
  174. call.start(this.backoffFactory);
  175. for (var i = 0; i < 3; i++) {
  176. this.backoff.emit('ready');
  177. }
  178. test.equals(this.callback.callCount, 0);
  179. this.backoff.emit('fail');
  180. test.ok(this.callback.calledWith(error));
  181. test.ok(this.wrappedFn.alwaysCalledWith(1, 2, 3));
  182. test.done();
  183. },
  184. "call should fail when the retry predicate returns false": function(test) {
  185. var call = new FunctionCall(this.wrappedFn, [1, 2, 3], this.callback);
  186. call.retryIf(function(err) { return err.retriable; });
  187. var retriableError = new Error();
  188. retriableError.retriable = true;
  189. var fatalError = new Error();
  190. fatalError.retriable = false;
  191. this.wrappedFn.
  192. onCall(0).yields(retriableError).
  193. onCall(1).yields(retriableError).
  194. onCall(2).yields(fatalError);
  195. call.start(this.backoffFactory);
  196. for (var i = 0; i < 2; i++) {
  197. this.backoff.emit('ready');
  198. }
  199. test.equals(this.callback.callCount, 1);
  200. test.ok(this.callback.calledWith(fatalError));
  201. test.ok(this.wrappedFn.alwaysCalledWith(1, 2, 3));
  202. test.done();
  203. },
  204. "wrapped function's callback shouldn't be called after abort": function(test) {
  205. var call = new FunctionCall(function(callback) {
  206. call.abort(); // Abort in middle of wrapped function's execution.
  207. callback(null, 'ok');
  208. }, [], this.callback);
  209. call.start(this.backoffFactory);
  210. test.equals(this.callback.callCount, 1,
  211. 'Wrapped function\'s callback shouldn\'t be called after abort.');
  212. test.ok(this.callback.calledWithMatch(sinon.match(function (err) {
  213. return !!err.message.match(/Backoff aborted/);
  214. }, "abort error")));
  215. test.done();
  216. },
  217. "abort event is emitted once when abort is called": function(test) {
  218. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  219. this.wrappedFn.yields(new Error());
  220. var callEventSpy = sinon.spy();
  221. call.on('abort', callEventSpy);
  222. call.start(this.backoffFactory);
  223. call.abort();
  224. call.abort();
  225. call.abort();
  226. test.equals(callEventSpy.callCount, 1);
  227. test.done();
  228. },
  229. "getLastResult should return the last intermediary result": function(test) {
  230. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  231. this.wrappedFn.yields(1);
  232. call.start(this.backoffFactory);
  233. for (var i = 2; i < 5; i++) {
  234. this.wrappedFn.yields(i);
  235. this.backoff.emit('ready');
  236. test.deepEqual([i], call.getLastResult());
  237. }
  238. this.wrappedFn.yields(null);
  239. this.backoff.emit('ready');
  240. test.deepEqual([null], call.getLastResult());
  241. test.done();
  242. },
  243. "getNumRetries should return the number of retries": function(test) {
  244. var call = new FunctionCall(this.wrappedFn, [], this.callback);
  245. this.wrappedFn.yields(1);
  246. call.start(this.backoffFactory);
  247. // The inital call doesn't count as a retry.
  248. test.equals(0, call.getNumRetries());
  249. for (var i = 2; i < 5; i++) {
  250. this.wrappedFn.yields(i);
  251. this.backoff.emit('ready');
  252. test.equals(i - 1, call.getNumRetries());
  253. }
  254. this.wrappedFn.yields(null);
  255. this.backoff.emit('ready');
  256. test.equals(4, call.getNumRetries());
  257. test.done();
  258. },
  259. "wrapped function's errors should be propagated": function(test) {
  260. var call = new FunctionCall(this.wrappedFn, [1, 2, 3], this.callback);
  261. this.wrappedFn.throws(new Error());
  262. test.throws(function() {
  263. call.start(this.backoffFactory);
  264. }, Error);
  265. test.done();
  266. },
  267. "wrapped callback's errors should be propagated": function(test) {
  268. var call = new FunctionCall(this.wrappedFn, [1, 2, 3], this.callback);
  269. this.wrappedFn.yields(null, 'Success!');
  270. this.callback.throws(new Error());
  271. test.throws(function() {
  272. call.start(this.backoffFactory);
  273. }, Error);
  274. test.done();
  275. },
  276. "call event should be emitted when wrapped function gets called": function(test) {
  277. this.wrappedFn.yields(1);
  278. var callEventSpy = sinon.spy();
  279. var call = new FunctionCall(this.wrappedFn, [1, 'two'], this.callback);
  280. call.on('call', callEventSpy);
  281. call.start(this.backoffFactory);
  282. for (var i = 1; i < 5; i++) {
  283. this.backoff.emit('ready');
  284. }
  285. test.equal(5, callEventSpy.callCount,
  286. 'The call event should have been emitted 5 times.');
  287. test.deepEqual([1, 'two'], callEventSpy.getCall(0).args,
  288. 'The call event should carry function\'s args.');
  289. test.done();
  290. },
  291. "callback event should be emitted when callback is called": function(test) {
  292. var call = new FunctionCall(this.wrappedFn, [1, 'two'], this.callback);
  293. var callbackSpy = sinon.spy();
  294. call.on('callback', callbackSpy);
  295. this.wrappedFn.yields('error');
  296. call.start(this.backoffFactory);
  297. this.wrappedFn.yields(null, 'done');
  298. this.backoff.emit('ready');
  299. test.equal(2, callbackSpy.callCount,
  300. 'Callback event should have been emitted 2 times.');
  301. test.deepEqual(['error'], callbackSpy.firstCall.args,
  302. 'First callback event should carry first call\'s results.');
  303. test.deepEqual([null, 'done'], callbackSpy.secondCall.args,
  304. 'Second callback event should carry second call\'s results.');
  305. test.done();
  306. },
  307. "backoff event should be emitted on backoff start": function(test) {
  308. var err = new Error('backoff event error');
  309. var call = new FunctionCall(this.wrappedFn, [1, 'two'], this.callback);
  310. var backoffSpy = sinon.spy();
  311. call.on('backoff', backoffSpy);
  312. this.wrappedFn.yields(err);
  313. call.start(this.backoffFactory);
  314. this.backoff.emit('backoff', 3, 1234, err);
  315. test.ok(this.backoff.backoff.calledWith(err),
  316. 'The backoff instance should have been called with the error.');
  317. test.equal(1, backoffSpy.callCount,
  318. 'Backoff event should have been emitted 1 time.');
  319. test.deepEqual([3, 1234, err], backoffSpy.firstCall.args,
  320. 'Backoff event should carry the backoff number, delay and error.');
  321. test.done();
  322. }
  323. };