/*
* 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");
}
}