/*
* Copyright 2012 Google Inc. All Rights Reserved.
*
* 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.errorprone.matchers;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.google.common.truth.Truth;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.CompileTimeConstant;
import com.google.errorprone.scanner.Scanner;
import com.google.errorprone.scanner.ScannerSupplier;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.tools.javac.main.Main.Result;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** {@link CompileTimeConstantExpressionMatcher}Test */
@RunWith(JUnit4.class)
public class CompileTimeConstantExpressionMatcherTest {
@Test
public void testMatches_matchesLiteralsAndStaticFinals() throws Exception {
String[] lines = {
"package test;",
"import com.google.errorprone.annotations.CompileTimeConstant;",
"public class CompileTimeConstantExpressionMatcherTestCase {",
" private final String final_string = \"bap\";",
" private final int final_int = 29;",
" private static final int static_final_int = 29;",
" public void m() { ",
" String s1; s1 = \"boop\"; s1 = \"boop\" + final_string;",
" int int2; int2 = 42;",
" Integer int3; int3 = 42 * final_int; int3 = 12 - static_final_int;",
" boolean bool4; bool4 = false;",
" }",
"}"
};
Map<String, Boolean> expectedMatches = new HashMap<String, Boolean>();
expectedMatches.put("s1", true);
expectedMatches.put("int2", true);
expectedMatches.put("int3", true);
expectedMatches.put("bool4", true);
assertCompilerMatchesOnAssignment(expectedMatches, lines);
}
@Test
public void testMatches_nullLiteral() throws Exception {
String[] lines = {
"package test;",
"import com.google.errorprone.annotations.CompileTimeConstant;",
"public class CompileTimeConstantExpressionMatcherTestCase {",
" private static final String static_final_string = null;",
" public void m() { ",
" String s1; s1 = null;",
" String s2; s2 = static_final_string;",
" }",
"}"
};
Map<String, Boolean> expectedMatches = new HashMap<String, Boolean>();
expectedMatches.put("s1", true);
// Even though s2 has the compile-time constant value "null", it's not
// a literal. I don't know how to distinguish this, but I doubt this is
// an important use case.
expectedMatches.put("s2", false);
assertCompilerMatchesOnAssignment(expectedMatches, lines);
}
@Test
public void testMatches_doesNotMatchNonLiterals() throws Exception {
String[] lines = {
"package test;",
"import com.google.errorprone.annotations.CompileTimeConstant;",
"public class CompileTimeConstantExpressionMatcherTestCase {",
" private final int nonfinal_int;",
" public CompileTimeConstantExpressionMatcherTestCase(int i) { ",
" nonfinal_int = i;",
" }",
" public void m(String s) { ",
" String s1; s1 = s;",
" int int2; int2 = s.length();",
" Integer int3; int3 = nonfinal_int; int3 = 14 * nonfinal_int;",
" boolean bool4; bool4 = false;",
" }",
"}"
};
Map<String, Boolean> expectedMatches = new HashMap<String, Boolean>();
expectedMatches.put("s1", false);
expectedMatches.put("int2", false);
expectedMatches.put("int3", false);
assertCompilerMatchesOnAssignment(expectedMatches, lines);
}
@Test
public void testMatches_finalCompileTimeConstantMethodParameters() throws Exception {
String[] lines = {
"package test;",
"import com.google.errorprone.annotations.CompileTimeConstant;",
"public class CompileTimeConstantExpressionMatcherTestCase {",
" public void m1(final @CompileTimeConstant String s) { ",
" String s1; s1 = s;",
" }",
" public void m2(@CompileTimeConstant String s) { ",
" s = null;",
" String s2; s2 = s;",
" }",
" public void m3(final String s) { ",
" String s3; s3 = s;",
" }",
" public void m4(@CompileTimeConstant String s) { ",
" String s4; s4 = s;",
" }",
"}"
};
Map<String, Boolean> expectedMatches = new HashMap<String, Boolean>();
expectedMatches.put("s1", true);
expectedMatches.put("s2", false);
expectedMatches.put("s3", false);
expectedMatches.put("s4", true);
assertCompilerMatchesOnAssignment(expectedMatches, lines);
}
@Test
public void testMatches_finalCompileTimeConstantConstructorParameters() throws Exception {
String[] lines = {
"package test;",
"import com.google.errorprone.annotations.CompileTimeConstant;",
"public class CompileTimeConstantExpressionMatcherTestCase {",
" public CompileTimeConstantExpressionMatcherTestCase(",
" final @CompileTimeConstant String s) { ",
" String s1; s1 = s;",
" }",
" public CompileTimeConstantExpressionMatcherTestCase(",
" @CompileTimeConstant String s, int foo) { ",
" s = null;",
" String s2; s2 = s;",
" }",
" public CompileTimeConstantExpressionMatcherTestCase(",
" final String s, boolean foo) { ",
" String s3; s3 = s;",
" }",
" public CompileTimeConstantExpressionMatcherTestCase(",
" @CompileTimeConstant String s, long foo) { ",
" String s4; s4 = s;",
" }",
"}"
};
Map<String, Boolean> expectedMatches = new HashMap<String, Boolean>();
expectedMatches.put("s1", true);
expectedMatches.put("s2", false);
expectedMatches.put("s3", false);
expectedMatches.put("s4", true);
assertCompilerMatchesOnAssignment(expectedMatches, lines);
}
// TODO(xtof): We'd like to eventually support other cases, but I first need
// to determine with confidence that the checker can ensure all initializations
// and assignments to such variables are compile-time-constant.
// For now, the annotation's target is restricted to ElementType.PARAMETER.
@Test
public void testCompileTimeConstantAnnotationOnlyAllowedOnParameter() throws Exception {
Truth.assertThat(CompileTimeConstant.class.getAnnotation(Target.class).value())
.isEqualTo(new ElementType[] {ElementType.PARAMETER});
}
// Helper methods.
private void assertCompilerMatchesOnAssignment(
final Map<String, Boolean> expectedMatches, String... lines) {
final Matcher<ExpressionTree> matcher = new CompileTimeConstantExpressionMatcher();
final Scanner scanner = new Scanner() {
@Override
public Void visitAssignment(AssignmentTree t, VisitorState state) {
ExpressionTree lhs = t.getVariable();
if (expectedMatches.containsKey(lhs.toString())) {
boolean matches = matcher.matches(t.getExpression(), state);
if (expectedMatches.get(lhs.toString())) {
assertTrue("Matcher should match expression" + t.getExpression(), matches);
} else {
assertFalse("Matcher should not match expression" + t.getExpression(), matches);
}
}
return super.visitAssignment(t, state);
}
};
CompilationTestHelper
.newInstance(ScannerSupplier.fromScanner(scanner), getClass())
.expectResult(Result.OK)
.addSourceLines("test/CompileTimeConstantExpressionMatcherTestCase.java", lines)
.doTest();
}
}