/* * Copyright 2008 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.common.collect.ImmutableList; import com.google.javascript.rhino.Node; import com.google.protobuf.TextFormat; import java.io.IOException; import java.util.List; /** * Tests for {@link InstrumentFunctions} * */ public final class InstrumentFunctionsTest extends CompilerTestCase { private String instrumentationPb; public InstrumentFunctionsTest() { this.instrumentationPb = null; } @Override protected void setUp() { this.instrumentationPb = null; } @Override protected CompilerPass getProcessor(Compiler compiler) { return new NameAndInstrumentFunctions(compiler); } @Override protected int getNumRepetitions() { // This pass is not idempotent. return 1; } public void testInstrument() { final String kPreamble = "var $$toRemoveDefinition1, $$notToRemove;\n" + "var $$toRemoveDefinition2, $$toRemoveDefinition3;\n"; // build instrumentation template and init code strings for use in // tests below. List<String> initCodeList = ImmutableList.of( "var $$Table = [];", "function $$TestDefine(id) {", " $$Table[id] = 0;", "};", "function $$TestInstrument(id) {", " $$Table[id]++;", "};"); StringBuilder initCodeBuilder = new StringBuilder(); StringBuilder pbBuilder = new StringBuilder(); for (String line : initCodeList) { initCodeBuilder.append(line).append("\n"); pbBuilder.append("init: \"").append(line).append("\"\n"); } pbBuilder.append("report_call: \"$$testInstrument\"") .append("report_defined: \"$$testDefine\"") .append("declaration_to_remove: \"$$toRemoveDefinition1\"") .append("declaration_to_remove: \"$$toRemoveDefinition2\"") .append("declaration_to_remove: \"$$toRemoveDefinition3\""); final String initCode = initCodeBuilder.toString(); this.instrumentationPb = pbBuilder.toString(); // Test basic instrumentation test("function a(){b}", initCode + "$$testDefine(0,\"a\");" + "function a(){$$testInstrument(0,\"a\",arguments);b}"); // Test declaration_to_remove test(kPreamble + "function a(){b}", initCode + "$$testDefine(0,\"a\");" + "var $$notToRemove;" + "function a(){$$testInstrument(0,\"a\",arguments);b}"); // Test object literal declarations test(kPreamble + "var a = { b: function(){c} }", initCode + "var $$notToRemove;" + "$$testDefine(0,\"<anonymous>\");" + "var a = { b: function(){" + "$$testInstrument(0,\"<anonymous>\",arguments);c} }"); // Test multiple object literal declarations test(kPreamble + "var a = { b: function(){c}, d: function(){e} }", initCode + "var $$notToRemove;" + "$$testDefine(0,\"<anonymous>\");" + "$$testDefine(1,\"<anonymous>\");" + "var a={b:function(){" + "$$testInstrument(0,\"<anonymous>\",arguments);c}," + "d:function(){$$testInstrument(1,\"<anonymous>\",arguments);e}}"); // Test recursive object literal declarations test(kPreamble + "var a = { b: { f: function(){c} }, d: function(){e} }", initCode + "var $$notToRemove;" + "$$testDefine(0,\"<anonymous>\");" + "$$testDefine(1,\"<anonymous>\");" + "var a={b:{f:function(){" + "$$testInstrument(0,\"<anonymous>\",arguments);c}}," + "d:function(){$$testInstrument(1,\"<anonymous>\",arguments);e}}"); } public void testEmpty() { this.instrumentationPb = ""; testSame("function a(){b}"); } public void testAppNameSetter() { this.instrumentationPb = "app_name_setter: \"setAppName\""; test("function a(){b}", "setAppName(\"testfile.js\");function a(){b}"); } public void testInit() { this.instrumentationPb = "init: \"var foo = 0;\"\n" + "init: \"function f(){g();}\"\n"; test("function a(){b}", "var foo = 0;function f(){g()}function a(){b}"); } public void testDeclare() { this.instrumentationPb = "report_defined: \"$$testDefine\""; test("function a(){b}", "$$testDefine(0,\"a\");function a(){b}"); } public void testCall() { this.instrumentationPb = "report_call: \"$$testCall\""; test("function a(){b}", "function a(){$$testCall(0,\"a\",arguments);b}"); } public void testNested() { this.instrumentationPb = "report_call: \"$$testCall\"\n" + "report_defined: \"$$testDefine\""; test("function a(){ function b(){}}", "$$testDefine(1,\"a\");$$testDefine(0,\"a::b\");" + "function a(){$$testCall(1,\"a\",arguments);" + "function b(){$$testCall(0,\"a::b\",arguments)}}"); } public void testExitPaths() { this.instrumentationPb = "report_exit: \"$$testExit\""; test("function a(){return}", "function a(){return $$testExit(0,undefined,\"a\")}"); test("function b(){return 5}", "function b(){return $$testExit(0,5,\"b\")}"); test("function a(){if(2 != 3){return}else{return 5}}", "function a(){if(2!=3){return $$testExit(0,undefined,\"a\")}" + "else{return $$testExit(0,5,\"a\")}}"); test("function a(){if(2 != 3){return}else{return 5}}b()", "function a(){if(2!=3){return $$testExit(0,undefined,\"a\")}" + "else{return $$testExit(0,5,\"a\")}}b()"); test("function a(){if(2 != 3){return}else{return 5}}", "function a(){if(2!=3){return $$testExit(0,undefined,\"a\")}" + "else{return $$testExit(0,5,\"a\")}}"); } public void testExitNoReturn() { this.instrumentationPb = "report_exit: \"$$testExit\""; test("function a(){}", "function a(){$$testExit(0,undefined,\"a\");}"); test("function a(){b()}", "function a(){b();$$testExit(0,undefined,\"a\");}"); } public void testPartialExitPaths() { this.instrumentationPb = "report_exit: \"$$testExit\""; test("function a(){if (2 != 3) {return}}", "function a(){if (2 != 3){return $$testExit(0,undefined,\"a\")}" + "$$testExit(0,undefined,\"a\")}"); } public void testExitTry() { this.instrumentationPb = "report_exit: \"$$testExit\""; test("function a(){try{return}catch(err){}}", "function a(){try{return $$testExit(0,undefined,\"a\")}catch(err){}" + "$$testExit(0,undefined,\"a\")}"); test("function a(){try{}catch(err){return}}", "function a(){try{}catch(err){return $$testExit(0,undefined,\"a\")}" + "$$testExit(0,undefined,\"a\")}"); test("function a(){try{return}finally{}}", "function a(){try{return $$testExit(0,undefined,\"a\")}finally{}" + "$$testExit(0,undefined,\"a\")}"); test("function a(){try{return}catch(err){}finally{}}", "function a(){try{return $$testExit(0,undefined,\"a\")}catch(err){}" + "finally{}$$testExit(0,undefined,\"a\")}"); test("function a(){try{return 1}catch(err){return 2}}", "function a(){try{return $$testExit(0,1,\"a\")}" + "catch(err){return $$testExit(0,2,\"a\")}}"); test("function a(){try{return 1}catch(err){return 2}finally{}}", "function a(){try{return $$testExit(0,1,\"a\")}" + "catch(err){return $$testExit(0,2,\"a\")}" + "finally{}$$testExit(0,undefined,\"a\")}"); test("function a(){try{return 1}catch(err){return 2}finally{return}}", "function a(){try{return $$testExit(0,1,\"a\")}" + "catch(err){return $$testExit(0,2,\"a\")}finally{" + "return $$testExit(0,undefined,\"a\")}}"); test("function a(){try{}catch(err){}finally{return}}", "function a(){try{}catch(err){}finally{" + "return $$testExit(0,undefined,\"a\")}}"); } public void testNestedExit() { this.instrumentationPb = "report_exit: \"$$testExit\"\n" + "report_defined: \"$$testDefine\""; test("function a(){ return function(){ return c;}}", "$$testDefine(1,\"a\");" + "function a(){$$testDefine(0,\"a::<anonymous>\");" + "return $$testExit(1,function(){" + "return $$testExit(0,c,\"a::<anonymous>\");},\"a\");}"); } private class NameAndInstrumentFunctions implements CompilerPass { private final Compiler compiler; NameAndInstrumentFunctions(Compiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { FunctionNames functionNames = new FunctionNames(compiler); functionNames.process(externs, root); Instrumentation.Builder builder = Instrumentation.newBuilder(); try { TextFormat.merge(instrumentationPb, builder); } catch (IOException e) { e.printStackTrace(); } InstrumentFunctions instrumentation = new InstrumentFunctions( compiler, functionNames, builder.build(), "testfile.js"); instrumentation.process(externs, root); } } }