disposable_test.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.DisposableTest');
  15. goog.setTestOnly('goog.DisposableTest');
  16. goog.require('goog.Disposable');
  17. goog.require('goog.testing.jsunit');
  18. goog.require('goog.testing.recordFunction');
  19. var d1, d2;
  20. // Sample subclass of goog.Disposable.
  21. function DisposableTest() {
  22. goog.Disposable.call(this);
  23. this.element = document.getElementById('someElement');
  24. }
  25. goog.inherits(DisposableTest, goog.Disposable);
  26. DisposableTest.prototype.disposeInternal = function() {
  27. DisposableTest.superClass_.disposeInternal.call(this);
  28. delete this.element;
  29. };
  30. // Class that doesn't inherit from goog.Disposable, but implements the
  31. // disposable interface via duck typing.
  32. function DisposableDuck() {
  33. this.element = document.getElementById('someElement');
  34. }
  35. DisposableDuck.prototype.dispose = function() {
  36. delete this.element;
  37. };
  38. // Class which calls dispose recursively.
  39. function RecursiveDisposable() {
  40. this.disposedCount = 0;
  41. }
  42. goog.inherits(RecursiveDisposable, goog.Disposable);
  43. RecursiveDisposable.prototype.disposeInternal = function() {
  44. ++this.disposedCount;
  45. assertEquals('Disposed too many times', 1, this.disposedCount);
  46. this.dispose();
  47. };
  48. // Test methods.
  49. function setUp() {
  50. d1 = new goog.Disposable();
  51. d2 = new DisposableTest();
  52. }
  53. function tearDown() {
  54. goog.Disposable.MONITORING_MODE = goog.Disposable.MonitoringMode.OFF;
  55. goog.Disposable.INCLUDE_STACK_ON_CREATION = true;
  56. goog.Disposable.instances_ = {};
  57. d1.dispose();
  58. d2.dispose();
  59. }
  60. function testConstructor() {
  61. assertFalse(d1.isDisposed());
  62. assertFalse(d2.isDisposed());
  63. assertEquals(document.getElementById('someElement'), d2.element);
  64. }
  65. function testDispose() {
  66. assertFalse(d1.isDisposed());
  67. d1.dispose();
  68. assertTrue(
  69. 'goog.Disposable instance should have been disposed of', d1.isDisposed());
  70. assertFalse(d2.isDisposed());
  71. d2.dispose();
  72. assertTrue(
  73. 'goog.DisposableTest instance should have been disposed of',
  74. d2.isDisposed());
  75. }
  76. function testDisposeInternal() {
  77. assertNotUndefined(d2.element);
  78. d2.dispose();
  79. assertUndefined(
  80. 'goog.DisposableTest.prototype.disposeInternal should ' +
  81. 'have deleted the element reference',
  82. d2.element);
  83. }
  84. function testDisposeAgain() {
  85. d2.dispose();
  86. assertUndefined(
  87. 'goog.DisposableTest.prototype.disposeInternal should ' +
  88. 'have deleted the element reference',
  89. d2.element);
  90. // Manually reset the element to a non-null value, and call dispose().
  91. // Because the object is already marked disposed, disposeInternal won't
  92. // be called again.
  93. d2.element = document.getElementById('someElement');
  94. d2.dispose();
  95. assertNotUndefined(
  96. 'disposeInternal should not be called again if the ' +
  97. 'object has already been marked disposed',
  98. d2.element);
  99. }
  100. function testDisposeWorksRecursively() {
  101. new RecursiveDisposable().dispose();
  102. }
  103. function testStaticDispose() {
  104. assertFalse(d1.isDisposed());
  105. goog.dispose(d1);
  106. assertTrue(
  107. 'goog.Disposable instance should have been disposed of', d1.isDisposed());
  108. assertFalse(d2.isDisposed());
  109. goog.dispose(d2);
  110. assertTrue(
  111. 'goog.DisposableTest instance should have been disposed of',
  112. d2.isDisposed());
  113. var duck = new DisposableDuck();
  114. assertNotUndefined(duck.element);
  115. goog.dispose(duck);
  116. assertUndefined(
  117. 'goog.dispose should have disposed of object that ' +
  118. 'implements the disposable interface',
  119. duck.element);
  120. }
  121. function testStaticDisposeOnNonDisposableType() {
  122. // Call goog.dispose() with various types and make sure no errors are
  123. // thrown.
  124. goog.dispose(true);
  125. goog.dispose(false);
  126. goog.dispose(null);
  127. goog.dispose(undefined);
  128. goog.dispose('');
  129. goog.dispose([]);
  130. goog.dispose({});
  131. function A() {}
  132. goog.dispose(new A());
  133. }
  134. function testMonitoringFailure() {
  135. function BadDisposable(){};
  136. goog.inherits(BadDisposable, goog.Disposable);
  137. goog.Disposable.MONITORING_MODE = goog.Disposable.MonitoringMode.PERMANENT;
  138. var badDisposable = new BadDisposable;
  139. assertArrayEquals(
  140. 'no disposable objects registered', [],
  141. goog.Disposable.getUndisposedObjects());
  142. assertThrows(
  143. 'the base ctor should have been called',
  144. goog.bind(badDisposable.dispose, badDisposable));
  145. }
  146. function testGetUndisposedObjects() {
  147. goog.Disposable.MONITORING_MODE = goog.Disposable.MonitoringMode.PERMANENT;
  148. var d1 = new DisposableTest();
  149. var d2 = new DisposableTest();
  150. assertSameElements(
  151. 'the undisposed instances', [d1, d2],
  152. goog.Disposable.getUndisposedObjects());
  153. d1.dispose();
  154. assertSameElements(
  155. '1 undisposed instance left', [d2],
  156. goog.Disposable.getUndisposedObjects());
  157. d1.dispose();
  158. assertSameElements(
  159. 'second disposal of the same object is no-op', [d2],
  160. goog.Disposable.getUndisposedObjects());
  161. d2.dispose();
  162. assertSameElements(
  163. 'all objects have been disposed of', [],
  164. goog.Disposable.getUndisposedObjects());
  165. }
  166. function testClearUndisposedObjects() {
  167. goog.Disposable.MONITORING_MODE = goog.Disposable.MonitoringMode.PERMANENT;
  168. var d1 = new DisposableTest();
  169. var d2 = new DisposableTest();
  170. d2.dispose();
  171. goog.Disposable.clearUndisposedObjects();
  172. assertSameElements(
  173. 'no undisposed object in the registry', [],
  174. goog.Disposable.getUndisposedObjects());
  175. assertThrows(
  176. 'disposal after clearUndisposedObjects()', function() { d1.dispose(); });
  177. // d2 is already disposed of, the redisposal shouldn't throw error.
  178. d2.dispose();
  179. }
  180. function testRegisterDisposable() {
  181. var d1 = new DisposableTest();
  182. var d2 = new DisposableTest();
  183. d1.registerDisposable(d2);
  184. d1.dispose();
  185. assertTrue('d2 should be disposed when d1 is disposed', d2.isDisposed());
  186. }
  187. function testDisposeAll() {
  188. var d1 = new DisposableTest();
  189. var d2 = new DisposableTest();
  190. goog.disposeAll(d1, d2);
  191. assertTrue('d1 should be disposed', d1.isDisposed());
  192. assertTrue('d2 should be disposed', d2.isDisposed());
  193. }
  194. function testDisposeAllRecursive() {
  195. var d1 = new DisposableTest();
  196. var d2 = new DisposableTest();
  197. var d3 = new DisposableTest();
  198. var d4 = new DisposableTest();
  199. goog.disposeAll(d1, [[d2], d3, d4]);
  200. assertTrue('d1 should be disposed', d1.isDisposed());
  201. assertTrue('d2 should be disposed', d2.isDisposed());
  202. assertTrue('d3 should be disposed', d3.isDisposed());
  203. assertTrue('d4 should be disposed', d4.isDisposed());
  204. }
  205. function testCreationStack() {
  206. if (!new Error().stack) return;
  207. goog.Disposable.MONITORING_MODE = goog.Disposable.MonitoringMode.PERMANENT;
  208. var disposableStack = new DisposableTest().creationStack;
  209. // Check that the name of this test function occurs in the stack trace.
  210. assertNotEquals(-1, disposableStack.indexOf('testCreationStack'));
  211. }
  212. function testMonitoredWithoutCreationStack() {
  213. if (!new Error().stack) return;
  214. goog.Disposable.MONITORING_MODE = goog.Disposable.MonitoringMode.PERMANENT;
  215. goog.Disposable.INCLUDE_STACK_ON_CREATION = false;
  216. var d1 = new DisposableTest();
  217. // Check that it is tracked, but not with a creation stack.
  218. assertUndefined(d1.creationStack);
  219. assertSameElements(
  220. 'the undisposed instance', [d1], goog.Disposable.getUndisposedObjects());
  221. }
  222. function testOnDisposeCallback() {
  223. var callback = goog.testing.recordFunction();
  224. d1.addOnDisposeCallback(callback);
  225. assertEquals('callback called too early', 0, callback.getCallCount());
  226. d1.dispose();
  227. assertEquals(
  228. 'callback should be called once on dispose', 1, callback.getCallCount());
  229. }
  230. function testOnDisposeCallbackOrder() {
  231. var invocations = [];
  232. var callback = function(str) { invocations.push(str); };
  233. d1.addOnDisposeCallback(goog.partial(callback, 'a'));
  234. d1.addOnDisposeCallback(goog.partial(callback, 'b'));
  235. goog.dispose(d1);
  236. assertArrayEquals(
  237. 'callbacks should be called in chronological order', ['a', 'b'],
  238. invocations);
  239. }
  240. function testAddOnDisposeCallbackAfterDispose() {
  241. var callback = goog.testing.recordFunction();
  242. var scope = {};
  243. goog.dispose(d1);
  244. d1.addOnDisposeCallback(callback, scope);
  245. assertEquals(
  246. 'Callback should be immediately called if already disposed', 1,
  247. callback.getCallCount());
  248. assertEquals(
  249. 'Callback scope should be respected', scope,
  250. callback.getLastCall().getThis());
  251. }
  252. function testInteractiveMonitoring() {
  253. var d1 = new DisposableTest();
  254. goog.Disposable.MONITORING_MODE = goog.Disposable.MonitoringMode.INTERACTIVE;
  255. var d2 = new DisposableTest();
  256. assertSameElements(
  257. 'only 1 undisposed instance tracked', [d2],
  258. goog.Disposable.getUndisposedObjects());
  259. // No errors should be thrown.
  260. d1.dispose();
  261. assertSameElements(
  262. '1 undisposed instance left', [d2],
  263. goog.Disposable.getUndisposedObjects());
  264. d2.dispose();
  265. assertSameElements(
  266. 'all disposed', [], goog.Disposable.getUndisposedObjects());
  267. }