/*
* Copyright 2015 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.dataflow.nullnesspropagation;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableSet;
import com.google.common.testing.ArbitraryInstances;
import com.google.errorprone.dataflow.nullnesspropagation.NullnessPropagationTransfer.MemberName;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests to verify assumptions about specific JDK and other methods and fields built into
* {@link NullnessPropagationTransfer} by running the referenced methods and reading the referenced
* fields where possible/feasible.
*
* @author kmb@google.com (Kevin Bierhoff)
*/
// TODO(kmb): Add tests for methods assumed to return non-null values (for all inputs...)
@RunWith(JUnit4.class)
public class NonNullAssumptionsTest {
@Test
public void testClassesWithNonNullStaticFields() throws Exception {
for (String classname : NullnessPropagationTransfer.CLASSES_WITH_NON_NULL_CONSTANTS) {
int found = 0;
Class<?> clazz = loadClass(classname);
for (Field field : clazz.getDeclaredFields()) {
if (Modifier.isFinal(field.getModifiers())
&& Modifier.isStatic(field.getModifiers())) {
++found;
field.setAccessible(true);
assertThat(field.get(null)).named(field.toString()).isNotNull();
}
}
assertWithMessage(classname).that(found).isGreaterThan(0);
}
}
@Test
public void testNullImpliesTrueParameters() throws Exception {
for (MemberName member : NullnessPropagationTransfer.NULL_IMPLIES_TRUE_PARAMETERS.keySet()) {
ImmutableSet<Integer> nullParameters =
NullnessPropagationTransfer.NULL_IMPLIES_TRUE_PARAMETERS.get(member);
assertWithMessage(member.clazz + "#" + member.member + "()")
.that(nullParameters)
.isNotEmpty();
int found = 0;
for (Method method : loadClass(member.clazz).getMethods()) {
if (!method.getName().equals(member.member)) {
continue;
}
++found;
for (int nullParam : nullParameters) {
// The following assertion would also fail if method returned something other than boolean
assertThat(invokeWithSingleNullArgument(method, nullParam)).isEqualTo(Boolean.TRUE);
}
}
assertWithMessage(member.clazz + "#" + member.member + "()").that(found).isGreaterThan(0);
}
}
@Test
public void testRequiredNonNullParameters() throws Exception {
for (MemberName member : NullnessPropagationTransfer.REQUIRED_NON_NULL_PARAMETERS.keySet()) {
ImmutableSet<Integer> nonNullParameters =
NullnessPropagationTransfer.REQUIRED_NON_NULL_PARAMETERS.get(member);
assertWithMessage(member.clazz + "#" + member.member + "()")
.that(nonNullParameters)
.isNotEmpty();
int found = 0;
for (Method method : loadClass(member.clazz).getMethods()) {
if (!method.getName().equals(member.member)) {
continue;
}
++found;
for (int nonNullParam : nonNullParameters) {
try {
invokeWithSingleNullArgument(method, nonNullParam);
fail("InvocationTargetException expected calling " + method + " with null parameter "
+ nonNullParam);
} catch (InvocationTargetException expected) {}
}
}
assertWithMessage(member.clazz + "#" + member.member + "()").that(found).isGreaterThan(0);
}
}
private static Object invokeWithSingleNullArgument(Method method, int nullParam)
throws IllegalAccessException, InvocationTargetException {
Class<?>[] params = method.getParameterTypes();
int nonNullIndex = nullParam < 0 ? params.length + nullParam : nullParam;
assertWithMessage(method.toString()).that(nonNullIndex).isLessThan(params.length);
Object[] args = new Object[params.length];
for (int i = 0; i < params.length; ++i) {
if (i != nonNullIndex) {
args[i] = ArbitraryInstances.get(params[i]);
}
}
Object receiver = Modifier.isStatic(method.getModifiers())
? null
: ArbitraryInstances.get(method.getDeclaringClass());
method.setAccessible(true);
return method.invoke(receiver, args);
}
private static Class<?> loadClass(String classname) throws Exception {
return Thread.currentThread().getContextClassLoader().loadClass(classname);
}
}