blob: 60bfc3e6afd11ea691e19c209b064d4b1cfb6ee5 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.android.tools.lint.detector.api;
import com.android.tools.lint.LintCoreApplicationEnvironment;
import com.android.utils.Pair;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.Disposer;
import com.intellij.psi.JavaRecursiveElementVisitor;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiLocalVariable;
import java.io.File;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
import junit.framework.TestCase;
import org.intellij.lang.annotations.Language;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.UFile;
import org.jetbrains.uast.UVariable;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
@SuppressWarnings("ClassNameDiffersFromFileName")
public class ConstantEvaluatorTest extends TestCase {
private static void checkJavaUast(
Object expected, @Language("JAVA") String source, final String targetVariable) {
Pair<JavaContext, Disposable> pair =
LintUtilsTest.parse(source, new File("src/test/pkg/Test.java"));
checkUast(expected, pair, source, targetVariable);
}
private static void checkKotlinUast(
Object expected, @Language("Kt") String source, final String targetVariable) {
Pair<JavaContext, Disposable> pair =
LintUtilsTest.parseKotlin(source, new File("src/test/pkg/Test.kt"));
checkUast(expected, pair, source, targetVariable);
}
private static void checkUast(
Object expected,
Pair<JavaContext, Disposable> pair,
String source,
final String targetVariable) {
JavaContext context = pair.getFirst();
Disposable disposable = pair.getSecond();
assertNotNull(context);
UFile uFile = context.getUastFile();
assertNotNull(uFile);
// Find the expression
final AtomicReference<UExpression> reference = new AtomicReference<>();
uFile.accept(
new AbstractUastVisitor() {
@Override
public boolean visitVariable(UVariable variable) {
String name = variable.getName();
if (name != null && name.equals(targetVariable)) {
reference.set(variable.getUastInitializer());
}
return super.visitVariable(variable);
}
});
UExpression expression = reference.get();
Object actual = ConstantEvaluator.evaluate(context, expression);
if (expected == null) {
assertNull(actual);
} else {
assertNotNull(
"Couldn't compute value for "
+ source
+ ", expected "
+ expected
+ " but was "
+ actual,
actual);
assertSame(expected.getClass(), actual.getClass());
if (expected instanceof Object[] && actual instanceof Object[]) {
assertEquals(
Arrays.toString((Object[]) expected), Arrays.toString((Object[]) actual));
assertTrue(Arrays.equals((Object[]) expected, (Object[]) actual));
} else if (expected instanceof int[] && actual instanceof int[]) {
assertEquals(Arrays.toString((int[]) expected), Arrays.toString((int[]) actual));
} else if (expected instanceof boolean[] && actual instanceof boolean[]) {
assertEquals(
Arrays.toString((boolean[]) expected), Arrays.toString((boolean[]) actual));
} else if (expected instanceof byte[] && actual instanceof byte[]) {
assertEquals(Arrays.toString((byte[]) expected), Arrays.toString((byte[]) actual));
} else {
assertEquals(expected.toString(), actual.toString());
assertEquals(expected, actual);
}
}
if (expected instanceof String) {
assertEquals(expected, ConstantEvaluator.evaluateString(context, expression, false));
}
Disposer.dispose(disposable);
}
private static void checkPsi(
Object expected, @Language("JAVA") String source, final String targetVariable) {
Pair<JavaContext, Disposable> pair =
LintUtilsTest.parse(source, new File("src/test/pkg/Test.java"));
JavaContext context = pair.getFirst();
Disposable disposable = pair.getSecond();
assertNotNull(context);
PsiFile javaFile = context.getPsiFile();
assertNotNull(javaFile);
// Find the expression
final AtomicReference<PsiExpression> reference = new AtomicReference<>();
javaFile.accept(
new JavaRecursiveElementVisitor() {
@Override
public void visitLocalVariable(PsiLocalVariable variable) {
super.visitLocalVariable(variable);
String name = variable.getName();
if (name != null && name.equals(targetVariable)) {
reference.set(variable.getInitializer());
}
}
});
PsiExpression expression = reference.get();
Object actual = ConstantEvaluator.evaluate(context, expression);
if (expected == null) {
assertNull(actual);
} else {
assertNotNull(
"Couldn't compute value for "
+ source
+ ", expected "
+ expected
+ " but was "
+ actual,
actual);
assertSame(expected.getClass(), actual.getClass());
if (expected instanceof Object[] && actual instanceof Object[]) {
assertEquals(
Arrays.toString((Object[]) expected), Arrays.toString((Object[]) actual));
assertTrue(Arrays.equals((Object[]) expected, (Object[]) actual));
} else if (expected instanceof int[] && actual instanceof int[]) {
assertEquals(Arrays.toString((int[]) expected), Arrays.toString((int[]) actual));
} else if (expected instanceof boolean[] && actual instanceof boolean[]) {
assertEquals(
Arrays.toString((boolean[]) expected), Arrays.toString((boolean[]) actual));
} else if (expected instanceof byte[] && actual instanceof byte[]) {
assertEquals(Arrays.toString((byte[]) expected), Arrays.toString((byte[]) actual));
} else {
assertEquals(expected.toString(), actual.toString());
assertEquals(expected, actual);
}
}
if (expected instanceof String) {
assertEquals(expected, ConstantEvaluator.evaluateString(context, expression, false));
}
Disposer.dispose(disposable);
}
private static void check(
Object expected, @Language("JAVA") String source, final String targetVariable) {
checkJavaUast(expected, source, targetVariable);
checkPsi(expected, source, targetVariable);
LintCoreApplicationEnvironment.disposeApplicationEnvironment();
}
private static void checkStatements(
Object expected, String statementsSource, final String targetVariable) {
@Language("JAVA")
String source =
""
+ "package test.pkg;\n"
+ "public class Test {\n"
+ " public void test() {\n"
+ " "
+ statementsSource
+ "\n"
+ " }\n"
+ " public static final int MY_INT_FIELD = 5;\n"
+ " public static final boolean MY_BOOLEAN_FIELD = true;\n"
+ " public static final String MY_STRING_FIELD = \"test\";\n"
+ "}\n";
check(expected, source, targetVariable);
}
private static void checkExpression(Object expected, String expressionSource) {
@Language("JAVA")
String source =
""
+ "package test.pkg;\n"
+ "public class Test {\n"
+ " public void test() {\n"
+ " Object expression = "
+ expressionSource
+ ";\n"
+ " }\n"
+ " public static final int MY_INT_FIELD = 5;\n"
+ " public static final boolean MY_BOOLEAN_FIELD = true;\n"
+ " public static final String MY_STRING_FIELD = \"test\";\n"
+ "}\n";
check(expected, source, "expression");
}
private static void checkKotlinExpression(Object expected, String expressionSource) {
@Language("Kt")
String source =
""
+ "package test.pkg\n"
+ "class Test {\n"
+ " fun test() {\n"
+ " val expression = "
+ expressionSource
+ "\n"
+ " }\n"
+ " const val MY_INT_FIELD = 5;\n"
+ " const val MY_BOOLEAN_FIELD = true;\n"
+ " const val MY_STRING_FIELD = \"test\";\n"
+ "}\n";
checkKotlinUast(expected, source, "expression");
LintCoreApplicationEnvironment.disposeApplicationEnvironment();
}
public void testStrings() {
checkExpression(null, "null");
checkExpression("hello", "\"hello\"");
checkExpression("abcd", "\"ab\" + \"cd\"");
}
public void testArrays() {
checkExpression(new int[] {1, 2, 3}, "new int[] { 1,2,3] }");
checkExpression(new int[0], "new int[0]");
checkExpression(new byte[0], "new byte[0]");
}
public void testLargeArrays() {
checkExpression(new ConstantEvaluator.ArrayReference(Byte.TYPE, 100, 2), "new byte[100][]");
checkExpression(new ConstantEvaluator.ArrayReference(Byte.TYPE, 100, 1), "new byte[100]");
checkExpression(
new ConstantEvaluator.ArrayReference("java.lang.Integer", 100, 1),
"new Integer[100]");
checkExpression(100, "(new byte[100]).length");
checkExpression(100, "(new Integer[100]).length");
}
public void testKotlin() {
checkKotlinExpression(
new ConstantEvaluator.ArrayReference(Integer.TYPE, 100, 1), "IntArray(100)");
checkKotlinExpression(100, "IntArray(100).size");
checkKotlinExpression(1000, "kotlin.Array<String>(1000).size");
checkKotlinExpression(
new ConstantEvaluator.ArrayReference(String.class, 1000, 1), "Array<String>(1000)");
checkKotlinExpression(
new ConstantEvaluator.ArrayReference(String.class, 1000, 1),
"kotlin.Array<String>(1000)");
checkKotlinExpression(new Integer[] {1, 2, 3, 4}, "arrayOf(1,2,3,4)");
checkKotlinExpression(3, "arrayOf(1,2,3,4)[2]");
checkKotlinExpression(4, "arrayOf(1,2,3,4).size");
checkKotlinExpression(
new ConstantEvaluator.ArrayReference(String.class, 1000, 1),
"arrayOfNulls<String>(1000)");
checkKotlinExpression(1000, "arrayOfNulls<String>(1000).size");
}
public void testBooleans() {
checkExpression(true, "true");
checkExpression(false, "false");
checkExpression(false, "false && true");
checkExpression(true, "false || true");
checkExpression(true, "!false");
}
public void testPolyadicBooleans() {
checkExpression(false, "false && true && true");
checkExpression(true, "false || false || true");
checkExpression(true, "false ^ false ^ true");
}
public void testChars() {
checkExpression('a', "'a'");
checkExpression('\007', "'\007'");
}
public void testCasts() {
checkExpression(1, "(int)1");
checkExpression(1L, "(long)1");
checkExpression(1, "(int)1.1f");
checkExpression((short) 65537, "(short)65537");
checkExpression((byte) 1023, "(byte)1023");
checkExpression(1.5, "(double)1.5f");
checkExpression(-5.0, "(double)-5");
}
public void testArithmetic() {
checkExpression(1, "1");
checkExpression(1L, "1L");
checkExpression(4, "1 + 3");
checkExpression(-2, "1 - 3");
checkExpression(10, "2 * 5");
checkExpression(2, "10 / 5");
checkExpression(1, "11 % 5");
checkExpression(8, "1 << 3");
checkExpression(16, "32 >> 1");
checkExpression(16, "32 >>> 1");
checkExpression(5, "5 | 1");
checkExpression(1, "5 & 1");
checkExpression(~5, "~5");
checkExpression(~(long) 5, "~(long)5");
checkExpression(~(short) 5, "~(short)5");
checkExpression(~(byte) 5, "~(byte)5");
checkExpression(-(long) 5, "-(long)5");
checkExpression(-(short) 5, "-(short)5");
checkExpression(-(byte) 5, "-(byte)5");
checkExpression(-(double) 5, "-(double)5");
checkExpression(-(float) 5, "-(float)5");
checkExpression(-2, "1 + -3");
checkExpression(false, "11 == 5");
checkExpression(true, "11 == 11");
checkExpression(true, "11 != 5");
checkExpression(false, "11 != 11");
checkExpression(true, "11 > 5");
checkExpression(false, "5 > 11");
checkExpression(false, "11 < 5");
checkExpression(true, "5 < 11");
checkExpression(true, "11 >= 5");
checkExpression(false, "5 >= 11");
checkExpression(false, "11 <= 5");
checkExpression(true, "5 <= 11");
checkExpression(3.5f, "1.0f + 2.5f");
}
public void testPolyadicArithmetic() {
checkExpression(9, "1 + 3 + 5");
checkExpression(94, "100 - 3 - 3");
checkExpression(100, "2 * 5 * 10");
checkExpression(1, "10 / 5 / 2");
checkExpression(16, "1 << 3 << 1");
checkExpression(8, "32 >> 1 >> 1");
checkExpression(8, "32 >>> 1 >>> 1");
checkExpression(5, "5 | 1 | 1");
checkExpression(1, "5 & 1 & 1");
checkExpression(true, "true && true && true");
}
public void testFieldReferences() {
checkExpression(5, "MY_INT_FIELD");
checkExpression("test", "MY_STRING_FIELD");
checkExpression("prefix-test-postfix", "\"prefix-\" + MY_STRING_FIELD + \"-postfix\"");
checkExpression(-4, "3 - (MY_INT_FIELD + 2)");
}
public void testStatements() {
checkStatements(
9,
""
+ "int x = +5;\n"
+ "int y = x;\n"
+ "int w;\n"
+ "w = -1;\n"
+ "int z = x + 5 + w;\n",
"z");
checkStatements(
"hello world",
""
+ "String initial = \"hello\";\n"
+ "String other;\n"
+ "other = \" world\";\n"
+ "String finalString = initial + other;\n",
"finalString");
}
public void testConditionals() {
checkStatements(
-5,
""
+ "boolean condition = false;\n"
+ "condition = !condition;\n"
+ "int z = condition ? -5 : 4;\n",
"z");
checkStatements(
-4, "boolean condition = true && false;\nint z = condition ? 5 : -4;\n", "z");
}
}