/* * Copyright 2012 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.CompilerOptions.DisposalCheckingPolicy; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import java.util.ArrayList; import java.util.List; /** * Tests for {@code CheckEventfulObjectDisposal.java}. * */ public final class CheckEventfulObjectDisposalTest extends CompilerTestCase { private DisposalCheckingPolicy policy = DisposalCheckingPolicy.ON; static final String CLOSURE_DEFS = "var goog = {};" + "goog.inherits = function(x, y) {};" + "/** @type {!Function} */ goog.abstractMethod = function() {};" + "goog.isArray = function(x) {};" + "goog.isDef = function(x) {};" + "goog.isFunction = function(x) {};" + "goog.isNull = function(x) {};" + "goog.isString = function(x) {};" + "goog.isObject = function(x) {};" + "goog.isDefAndNotNull = function(x) {};" + "goog.asserts = {};" + "goog.dispose = function(x) {};" + "goog.disposeAll = function(var_args) {};" + "/** @return {*} */ goog.asserts.assert = function(x) { return x; };" + "goog.disposable = {};" + "/** @interface */\n" + "goog.disposable.IDisposable = function() {};" + "goog.disposable.IDisposable.prototype.dispose;" + "/** @implements {goog.disposable.IDisposable}\n * @constructor */\n" + "goog.Disposable = goog.abstractMethod;" + "/** @override */" + "goog.Disposable.prototype.dispose = goog.abstractMethod;" + "/** @param {goog.Disposable} fn */" + "goog.Disposable.prototype.registerDisposable = goog.abstractMethod;" + "goog.events = {};" + "/** @extends {goog.Disposable}\n * @constructor */" + "goog.events.EventHandler = function() {};" + "goog.events.EventHandler.prototype.removeAll = function() {};"; @Override protected void setUp() throws Exception { super.setUp(); enableTypeCheck(); setAcceptedLanguage(LanguageMode.ECMASCRIPT5); } @Override protected CompilerPass getProcessor(Compiler compiler) { CheckEventfulObjectDisposal compilerPass = new CheckEventfulObjectDisposal(compiler, policy); return compilerPass; } public void testNoEventHandler() { String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testNotFreed1() { String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { this.eh = new goog.events.EventHandler(); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testError(js, CheckEventfulObjectDisposal.EVENTFUL_OBJECT_NOT_DISPOSED); } public void testAlmostTooManyVariables() { policy = DisposalCheckingPolicy.AGGRESSIVE; List<String> parts = new ArrayList<>(); parts.add(CLOSURE_DEFS); parts.add("var test = function() {"); parts.add(" var eh = new goog.events.EventHandler();"); for (int i = 0; i < LiveVariablesAnalysis.MAX_VARIABLES_TO_ANALYZE - 1; i++) { parts.add(" var v" + i + " = null;"); } parts.add("}"); testError(LINE_JOINER.join(parts), CheckEventfulObjectDisposal.EVENTFUL_OBJECT_PURELY_LOCAL); } public void testTooManyVariables() { policy = DisposalCheckingPolicy.AGGRESSIVE; List<String> parts = new ArrayList<>(); parts.add(CLOSURE_DEFS); parts.add("var test = function() {"); parts.add(" var eh = new goog.events.EventHandler();"); for (int i = 0; i < LiveVariablesAnalysis.MAX_VARIABLES_TO_ANALYZE; i++) { parts.add(" var v" + i + " = null;"); } parts.add("}"); testSame(LINE_JOINER.join(parts)); } public void testLocal() { String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { var eh = new goog.events.EventHandler();\n" + "};\n" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testLocalAggressive() { policy = DisposalCheckingPolicy.AGGRESSIVE; String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { var eh = new goog.events.EventHandler();\n" + "};\n" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testError(js, CheckEventfulObjectDisposal.EVENTFUL_OBJECT_PURELY_LOCAL); } public void testFreedLocal1() { policy = DisposalCheckingPolicy.AGGRESSIVE; String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { var eh = new goog.events.EventHandler();" + "eh.dispose(); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testEventhandlerRemoveAll1() { policy = DisposalCheckingPolicy.AGGRESSIVE; String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { this.eh = new goog.events.EventHandler(); };" + "test.prototype.free = function() { this.eh.removeAll(); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testEventhandlerRemoveAll2() { policy = DisposalCheckingPolicy.AGGRESSIVE; String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { var eh = new goog.events.EventHandler();" + "eh.removeAll(); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testFreedLocal2() { String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { var eh = new goog.events.EventHandler();" + "this.registerDisposable(eh); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testFreedLocal2Aggressive() { policy = DisposalCheckingPolicy.AGGRESSIVE; String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { var eh = new goog.events.EventHandler();" + "this.registerDisposable(eh); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testError(js, CheckEventfulObjectDisposal.EVENTFUL_OBJECT_PURELY_LOCAL); } public void testLocalLive1() { policy = DisposalCheckingPolicy.AGGRESSIVE; String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { var eh = new goog.events.EventHandler();" + "this.eh = eh;" + "eh.dispose(); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testLocalLive2() { policy = DisposalCheckingPolicy.AGGRESSIVE; String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { var eh = new goog.events.EventHandler();" + "this.eh = eh;" + "this.eh.dispose(); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } /* * Local variable is never freed but as it is assigned to an array * this is left to the dynamic analyzer to discover it. */ public void testLocalLive3() { policy = DisposalCheckingPolicy.AGGRESSIVE; String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { var eh = new goog.events.EventHandler();" + "this.ehs = [];" + "this.ehs[0] = eh;" + "};" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testFreedDispose() { String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { this.eh = new goog.events.EventHandler();" + "this.eh.dispose(); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testFreedGoogDispose1() { String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { this.eh = new goog.events.EventHandler();" + "goog.dispose(this.eh); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testNotAllFreedGoogDispose() { String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() {" + "this.eh1 = new goog.events.EventHandler();" + "this.eh2 = new goog.events.EventHandler();" + "goog.dispose(this.eh1, this.eh2); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testError(js, CheckEventfulObjectDisposal.EVENTFUL_OBJECT_NOT_DISPOSED); } public void testFreedGoogDisposeAll() { String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { " + "this.eh1 = new goog.events.EventHandler();" + "this.eh2 = new goog.events.EventHandler();" + "goog.disposeAll(this.eh1, this.eh2); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testFreedRegisterDisposable() { String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { this.eh = new goog.events.EventHandler();" + "this.registerDisposable(this.eh); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testFreedRemoveAll() { String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { this.eh = new goog.events.EventHandler();" + "this.eh.removeAll(); };" + "goog.inherits(test, goog.Disposable);" + "var testObj = new test();"; testSame(js); } public void testPrivateInheritance() { String js = CLOSURE_DEFS + "/** @extends {goog.Disposable}\n * @constructor */" + "var test = function() { " + "/** @private */ this.eh = new goog.events.EventHandler();" + "this.eh.removeAll(); };" + "goog.inherits(test, goog.Disposable);" + "/** @extends {test}\n * @constructor */" + "var subclass = function() {" + "/** @private */ this.eh = new goog.events.EventHandler();" + "this.eh.dispose();" + "};" + "var testObj = new test();"; testError(js, CheckEventfulObjectDisposal.OVERWRITE_PRIVATE_EVENTFUL_OBJECT); } public void testCustomDispose1() { policy = DisposalCheckingPolicy.AGGRESSIVE; String js = CLOSURE_DEFS + "/** @param todispose\n @param ctx\n @disposes todispose\n */" + "customDispose = function(todispose, ctx) {" + " ctx.registerDisposable(todispose);" + " return todispose;" + "};" + "var x = new goog.events.EventHandler();" + "customDispose(x, OBJ);"; testSame(js); } public void testCustomDispose2() { policy = DisposalCheckingPolicy.AGGRESSIVE; String js = CLOSURE_DEFS + "/** @param todispose\n @param ctx\n @disposes *\n */" + "customDispose = function(todispose, ctx) {" + " ctx.registerDisposable(todispose);" + " return todispose;" + "};" + "var x = new goog.events.EventHandler();" + "var y = new goog.events.EventHandler();" + "customDispose(x, y);"; testSame(js); } public void testCustomDispose3() { policy = DisposalCheckingPolicy.AGGRESSIVE; String js = CLOSURE_DEFS + "/** @param todispose\n @param ctx\n @disposes todispose\n */" + "customDispose = function(todispose, ctx) {" + " ctx.registerDisposable(todispose);" + " return todispose;" + "};" + "var x = new goog.events.EventHandler();" + "customDispose(OBJ, x);"; testError(js, CheckEventfulObjectDisposal.EVENTFUL_OBJECT_PURELY_LOCAL); } }