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