/* * 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.devtools.j2objc.translate; import com.google.devtools.j2objc.GenerationTest; import java.io.IOException; /** * Unit tests for {@link ConstantBranchPruner}. * * @author Tom Ball */ public class ConstantBranchPrunerTest extends GenerationTest { // Verify that statements with non-constant boolean expressions aren't modified. public void testNoPruning() throws IOException { String translation = translateSourceFile( "class Test { " + "int test(boolean b) { if (b) { return 1; } else { return 0; }}" + "String describe() { return \"is true? \" + true; }}", "Test", "Test.m"); assertTranslatedLines(translation, "if (b) {", "return 1;", "}", "else {", "return 0;", "}"); assertTranslation(translation, "return @\"is true? true\";"); translation = translateSourceFile( "class Test { void tick() {} void test(boolean b) { while (b) { tick(); } }}", "Test", "Test.m"); assertTranslatedLines(translation, "while (b) {", "[self tick];", "}"); translation = translateSourceFile( "class Test { void tick() {} void test(boolean b) { do { tick(); } while (b); }}", "Test", "Test.m"); assertTranslatedLines(translation, "do {", "[self tick];", "}", "while (b);"); } // Do statements should not be replaced with their body because they might contain break // statements. public void testFalseDoExpression() throws IOException { String translation = translateSourceFile( "class Test { int test() { foo: do { return 1; } while (false); }}", "Test", "Test.m"); assertTranslatedLines(translation, "- (jint)test {", "foo: do {", "return 1;", "}", "while (false);", "}"); translation = translateSourceFile( "class Test { static final boolean debug = false;" + " int test() { foo: do { return 1; } while (debug); }}", "Test", "Test.m"); assertTranslatedLines(translation, "- (jint)test {", "foo: do {", "return 1;", "}", "while (Test_debug);", "}"); // Can't remove loop construct while it contains a break statement. translation = translateSourceFile( "class Test { void test(int i) { do { if (i == 5) break; } while (false); } }", "Test", "Test.m"); assertTranslatedLines(translation, "do {", "if (i == 5) break;", "}", "while (false);"); } // Verify then block replaces if statement when true. public void testTrueIfExpression() throws IOException { String translation = translateSourceFile( "class Test { int test() { if (true) { return 1; } else { return 0; } }}", "Test", "Test.m"); assertTranslatedLines(translation, "- (jint)test {", "{", "return 1;", "}", "}"); translation = translateSourceFile( "class Test { static final boolean debug = true;" + " int test() { if (debug) { return 1; } else { return 0; } }}", "Test", "Test.m"); assertTranslatedLines(translation, "- (jint)test {", "{", "return 1;", "}", "}"); } // Verify else block replaces if statement when false. public void testFalseIfExpression() throws IOException { String translation = translateSourceFile( "class Test { int test() { if (false) { return 1; } else { return 0; } }}", "Test", "Test.m"); assertTranslatedLines(translation, "- (jint)test {", "{", "return 0;", "}", "}"); translation = translateSourceFile( "class Test { static final boolean debug = false;" + " int test() { if (debug) { return 1; } else { return 0; } }}", "Test", "Test.m"); assertTranslatedLines(translation, "- (jint)test {", "{", "return 0;", "}", "}"); } // Verify parentheses surround boolean constants are removed. public void testParentheses() throws IOException { String translation = translateSourceFile( "class Test { int test() { if (((false))) { return 1; } else { return 0; } }}", "Test", "Test.m"); assertTranslatedLines(translation, "- (jint)test {", "{", "return 0;", "}", "}"); } // Verify ! boolean constant is inverted. public void testBooleanInversion() throws IOException { String translation = translateSourceFile( "class Test { static final boolean debug = true;" + " int test() { if (!(debug)) { return 1; } else { return 0; } }}", "Test", "Test.m"); assertTranslatedLines(translation, "- (jint)test {", "{", "return 0;", "}", "}"); } // Verify && expressions are pruned correctly. public void testAnd() throws IOException { String translation = translateSourceFile( "class Test { " + "static final boolean DEBUG = true; " + "static final boolean NDEBUG = false;" + "int test() { int result; " + " if (DEBUG && true) { result = 1; } else { result = 2; }" + " if (DEBUG && false) { result = 3; } else { result = 4; }" + " if (true && NDEBUG) { result = 5; } else { result = 6; }" + " if (false && NDEBUG) { result = 7; } else { result = 8; }" + " return result; }}", "Test", "Test.m"); assertTranslatedLines(translation, "{", "result = 1;", "}", "{", "result = 4;", "}", "{", "result = 6;", "}", "{", "result = 8;", "}"); } // Verify || expressions are pruned correctly. public void testOr() throws IOException { String translation = translateSourceFile( "class Test { " + "static final boolean DEBUG = true; " + "static final boolean NDEBUG = false;" + "int test() { int result; " + " if (DEBUG || true) { result = 1; } else { result = 2; }" + " if (DEBUG || false) { result = 3; } else { result = 4; }" + " if (true || NDEBUG) { result = 5; } else { result = 6; }" + " if (false || NDEBUG) { result = 7; } else { result = 8; }" + " return result; }}", "Test", "Test.m"); assertTranslatedLines(translation, "{", "result = 1;", "}", "{", "result = 3;", "}", "{", "result = 5;", "}", "{", "result = 8;", "}"); } // Verify method invocation paired with constant is not pruned because methods // may have side effects. public void testMethodAnd() throws IOException { String translation = translateSourceFile( "class Test { " + "static final boolean DEBUG = true; " + "static final boolean NDEBUG = false;" + "boolean b;" + "boolean enabled() { return true; }" + "int test() { int result; " // The "b" operand should be pruned since it has no side effect. + " if (enabled() && b && NDEBUG) { result = 1; } else { result = 2; }" + " if (enabled() || DEBUG) { result = 3; } else { result = 4; }" + " return result; }}", "Test", "Test.m"); assertTranslatedLines(translation, "[self enabled];", "{", " result = 2;", "}", "[self enabled];", "{", " result = 3;", "}"); } public void testWhileStatementPrunedWithSideEffects() throws IOException { String translation = translateSourceFile( "class Test { boolean getB() { return true; } int test(boolean b) { " + "while (b && (getB() && false)) { return 1; } return 0; } }", "Test", "Test.m"); assertTranslatedLines(translation, "- (jint)testWithBoolean:(jboolean)b {", " b && ([self getB]);", " return 0;", "}"); } public void testExpressionPruning() throws IOException { String translation = translateSourceFile( "class A { " + "static final boolean DEBUG = true; " + "static final boolean TEST = true; " + "private static boolean nonConstant = false; " + "boolean test() { " // DEBUG and TEST constants should be pruned. + " if (DEBUG && TEST && nonConstant) return false; " + " return true; }}", "A", "A.m"); assertTranslatedLines(translation, "- (jboolean)test {", "if (A_nonConstant) return false;", "return true;", "}"); } // Verify that volatile loads aren't pruned because they provide a memory // barrier. public void testVolatileLoad() throws IOException { String translation = translateSourceFile( "class Test { volatile int i; boolean test() { return i == 1 && false; } }", "Test", "Test.m"); assertTranslation(translation, "return JreLoadVolatileInt(&i_) == 1 && false;"); } // The JDT parser provides limited type information for local types declared // in unreachable code, resulting in NPEs. ConstantBranchPruner should remove // this unreachable code. public void testUnreachableLocalClass() throws IOException { String translation = translateSourceFile( "class Test { int test() { if (true) { return 5; } " + "class Foo {}; return new Foo().hashCode(); } }", "Test", "Test.m"); assertNotInTranslation(translation, "Foo"); assertTranslatedLines(translation, "- (jint)test {", " {", " return 5;", " }", "}"); } public void testConditionalExpressions() throws IOException { String translation = translateSourceFile( "class Test { " + "private static final boolean IS_RI = true; " + "public static final int SIZE = IS_RI ? 1 : 10;" + "public static final int LENGTH = !IS_RI ? 42 : 666; }", "Test", "Test.h"); // Verify true constant replaces the conditional with the then expression. assertTranslation(translation, "Test_SIZE 1"); // Verify false constant replaces the conditional with the else expression. assertTranslation(translation, "Test_LENGTH 666"); } }