/*
* 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.bugpatterns;
import com.google.common.io.ByteStreams;
import com.google.errorprone.CompilationTestHelper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* @author eaftan@google.com (Eddie Aftandilian)
*/
@RunWith(JUnit4.class)
public class CheckReturnValueTest {
private CompilationTestHelper compilationHelper;
@Before
public void setUp() {
compilationHelper = CompilationTestHelper.newInstance(CheckReturnValue.class, getClass());
}
@Test
public void testPositiveCases() throws Exception {
compilationHelper.addSourceFile("CheckReturnValuePositiveCases.java").doTest();
}
@Test
public void testCustomCheckReturnValueAnnotation() {
compilationHelper
.addSourceLines(
"foo/bar/CheckReturnValue.java",
"package foo.bar;",
"public @interface CheckReturnValue {}")
.addSourceLines(
"test/TestCustomCheckReturnValueAnnotation.java",
"package test;",
"import foo.bar.CheckReturnValue;",
"public class TestCustomCheckReturnValueAnnotation {",
" @CheckReturnValue",
" public String getString() {",
" return \"string\";",
" }",
" public void doIt() {",
" // BUG: Diagnostic contains:",
" getString();",
" }",
"}")
.doTest();
}
@Test
public void testNegativeCase() throws Exception {
compilationHelper.addSourceFile("CheckReturnValueNegativeCases.java").doTest();
}
@Test
public void testPackageAnnotation() throws Exception {
compilationHelper
.addSourceLines(
"package-info.java",
"@javax.annotation.CheckReturnValue",
"package lib;")
.addSourceLines(
"lib/Lib.java",
"package lib;",
"public class Lib {",
" public static int f() { return 42; }",
"}")
.addSourceLines(
"Test.java",
"class Test {",
" void m() {",
" // BUG: Diagnostic contains: Ignored return value",
" lib.Lib.f();",
" }",
"}")
.doTest();
}
@Test
public void testClassAnnotation() throws Exception {
compilationHelper
.addSourceLines(
"lib/Lib.java",
"package lib;",
"@javax.annotation.CheckReturnValue",
"public class Lib {",
" public static int f() { return 42; }",
"}")
.addSourceLines(
"Test.java",
"class Test {",
" void m() {",
" // BUG: Diagnostic contains: Ignored return value",
" lib.Lib.f();",
" }",
"}")
.doTest();
}
// Don't match void-returning methods in packages with @CRV
@Test
public void testVoidReturningMethodInAnnotatedPackage() throws Exception {
compilationHelper
.addSourceLines(
"package-info.java",
"@javax.annotation.CheckReturnValue",
"package lib;")
.addSourceLines(
"lib/Lib.java",
"package lib;",
"public class Lib {",
" public static void f() {}",
"}")
.addSourceLines(
"Test.java",
"class Test {",
" void m() {",
" lib.Lib.f();",
" }",
"}")
.doTest();
}
@Test
public void badCRVOnProcedure() throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"package lib;",
"@javax.annotation.CheckReturnValue",
"public class Test {",
" // BUG: Diagnostic contains:",
" // @CheckReturnValue may not be applied to void-returning methods",
" @javax.annotation.CheckReturnValue public static void f() {}",
"}")
.doTest();
}
@Test
public void badCRVOnPseudoProcedure() throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"package lib;",
"@javax.annotation.CheckReturnValue",
"public class Test {",
" // BUG: Diagnostic contains:",
" // @CheckReturnValue may not be applied to void-returning methods",
" @javax.annotation.CheckReturnValue public static Void f() {",
" return null;",
" }",
"}")
.doTest();
}
// Don't match methods invoked through {@link org.mockito.Mockito}.
@Test
public void testIgnoreCRVOnMockito() throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"package lib;",
"public class Test {",
" @javax.annotation.CheckReturnValue",
" public int f() {",
" return 0;",
" }",
"}")
.addSourceLines(
"TestCase.java",
"import static org.mockito.Mockito.verify;",
"import static org.mockito.Mockito.doReturn;",
"import org.mockito.Mockito;",
"class TestCase {",
" void m() {",
" lib.Test t = new lib.Test();",
" Mockito.verify(t).f();",
" verify(t).f();",
" doReturn(1).when(t).f();",
" Mockito.doReturn(1).when(t).f();",
" }",
"}")
.doTest();
}
@Test
public void testPackageAnnotationButCanIgnoreReturnValue() throws Exception {
compilationHelper
.addSourceLines(
"package-info.java",
"@javax.annotation.CheckReturnValue",
"package lib;")
.addSourceLines(
"lib/Lib.java",
"package lib;",
"public class Lib {",
" @com.google.errorprone.annotations.CanIgnoreReturnValue",
" public static int f() { return 42; }",
"}")
.addSourceLines(
"Test.java",
"class Test {",
" void m() {",
" lib.Lib.f();",
" }",
"}")
.doTest();
}
@Test
public void testClassAnnotationButCanIgnoreReturnValue() throws Exception {
compilationHelper
.addSourceLines(
"lib/Lib.java",
"package lib;",
"@javax.annotation.CheckReturnValue",
"public class Lib {",
" @com.google.errorprone.annotations.CanIgnoreReturnValue",
" public static int f() { return 42; }",
"}")
.addSourceLines(
"Test.java",
"class Test {",
" void m() {",
" lib.Lib.f();",
" }",
"}")
.doTest();
}
@Test
public void badCanIgnoreReturnValueOnProcedure() throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"package lib;",
"@javax.annotation.CheckReturnValue",
"public class Test {",
" // BUG: Diagnostic contains:",
" // @CanIgnoreReturnValue may not be applied to void-returning methods",
" @com.google.errorprone.annotations.CanIgnoreReturnValue public static void f() {}",
"}")
.doTest();
}
@Test
public void testNestedClassAnnotation() throws Exception {
compilationHelper
.addSourceLines(
"lib/Lib.java",
"package lib;",
"@javax.annotation.CheckReturnValue",
"public class Lib {",
" public static class Inner {",
" public static class InnerMost {",
" public static int f() { return 42; }",
" }",
" }",
"}")
.addSourceLines(
"Test.java",
"class Test {",
" void m() {",
" // BUG: Diagnostic contains: Ignored return value",
" lib.Lib.Inner.InnerMost.f();",
" }",
"}")
.doTest();
}
@Test
public void testNestedClassWithCanIgnoreAnnotation() throws Exception {
compilationHelper
.addSourceLines(
"lib/Lib.java",
"package lib;",
"@javax.annotation.CheckReturnValue",
"public class Lib {",
" @com.google.errorprone.annotations.CanIgnoreReturnValue",
" public static class Inner {",
" public static class InnerMost {",
" public static int f() { return 42; }",
" }",
" }",
"}")
.addSourceLines("Test.java",
"class Test {",
" void m() {",
" lib.Lib.Inner.InnerMost.f();",
" }",
"}")
.doTest();
}
@Test
public void testPackageWithCanIgnoreAnnotation() throws Exception {
compilationHelper
.addSourceLines(
"package-info.java",
"@javax.annotation.CheckReturnValue",
"package lib;")
.addSourceLines("lib/Lib.java",
"package lib;",
"@com.google.errorprone.annotations.CanIgnoreReturnValue",
"public class Lib {",
" public static int f() { return 42; }",
"}")
.addSourceLines("Test.java",
"class Test {",
" void m() {",
" lib.Lib.f();",
" }",
"}")
.doTest();
}
@Test
public void errorBothClass() throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"@com.google.errorprone.annotations.CanIgnoreReturnValue",
"@javax.annotation.CheckReturnValue",
"// BUG: Diagnostic contains: @CheckReturnValue and @CanIgnoreReturnValue cannot"
+ " both be applied to the same class",
"class Test {}")
.doTest();
}
@Test
public void errorBothMethod() throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"class Test {",
" @com.google.errorprone.annotations.CanIgnoreReturnValue",
" @javax.annotation.CheckReturnValue",
" // BUG: Diagnostic contains: @CheckReturnValue and @CanIgnoreReturnValue cannot"
+ " both be applied to the same method",
" void m() {}",
"}")
.doTest();
}
// Don't match Void-returning methods in packages with @CRV
@Test
public void testJavaLangVoidReturningMethodInAnnotatedPackage() throws Exception {
compilationHelper
.addSourceLines(
"package-info.java",
"@javax.annotation.CheckReturnValue",
"package lib;")
.addSourceLines(
"lib/Lib.java",
"package lib;",
"public class Lib {",
" public static Void f() {",
" return null;",
" }",
"}")
.addSourceLines(
"Test.java",
"class Test {",
" void m() {",
" lib.Lib.f();",
" }",
"}")
.doTest();
}
@Test
public void ignoreInTests() throws Exception {
compilationHelper
.addSourceLines(
"Foo.java",
"@javax.annotation.CheckReturnValue",
"public class Foo {",
" public int f() {",
" return 42;",
" }",
"}")
.addSourceLines(
"Test.java",
"class Test {",
" void f(Foo foo) {",
" try {",
" foo.f();",
" org.junit.Assert.fail();",
" } catch (Exception expected) {}",
" try {",
" foo.f();",
" junit.framework.Assert.fail();",
" } catch (Exception expected) {}",
" try {",
" foo.f();",
" junit.framework.TestCase.fail();",
" } catch (Exception expected) {}",
" }",
"}")
.doTest();
}
@Test
public void ignoreInTestsWithRule() throws Exception {
compilationHelper
.addSourceLines(
"Foo.java",
"@javax.annotation.CheckReturnValue",
"public class Foo {",
" public int f() {",
" return 42;",
" }",
"}")
.addSourceLines(
"Test.java",
"class Test {",
" private org.junit.rules.ExpectedException exception;",
" void f(Foo foo) {",
" exception.expect(IllegalArgumentException.class);",
" foo.f();",
" }",
"}")
.doTest();
}
@Test
public void ignoreInTestsWithFailureMessage() throws Exception {
compilationHelper
.addSourceLines(
"Foo.java",
"@javax.annotation.CheckReturnValue",
"public class Foo {",
" public int f() {",
" return 42;",
" }",
"}")
.addSourceLines(
"Test.java",
"class Test {",
" void f(Foo foo) {",
" try {",
" foo.f();",
" org.junit.Assert.fail(\"message\");",
" } catch (Exception expected) {}",
" try {",
" foo.f();",
" junit.framework.Assert.fail(\"message\");",
" } catch (Exception expected) {}",
" try {",
" foo.f();",
" junit.framework.TestCase.fail(\"message\");",
" } catch (Exception expected) {}",
" }",
"}")
.doTest();
}
@Test
public void ignoreInAssertThrowsBodies() throws Exception {
compilationHelper
.addSourceLines(
"Foo.java",
"@javax.annotation.CheckReturnValue",
"public class Foo {",
" public int f() {",
" return 42;",
" }",
"}")
.addSourceLines(
"Test.java",
"class Test {",
" void f(Foo foo) {",
" org.junit.Assert.assertThrows(IllegalStateException.class, ",
" new org.junit.function.ThrowingRunnable() {",
" @Override",
" public void run() throws Throwable {",
" foo.f();",
" }",
" });",
" org.junit.Assert.assertThrows(IllegalStateException.class, () -> foo.f());",
" org.junit.Assert.assertThrows(IllegalStateException.class, () -> {",
" int bah = foo.f();",
" foo.f(); ",
" });",
" org.junit.Assert.assertThrows(IllegalStateException.class, () -> { ",
" // BUG: Diagnostic contains: Ignored return value",
" foo.f(); ",
" foo.f(); ",
" });",
" }",
"}")
.doTest();
}
@Test
public void ignoreTruthFailure() throws Exception {
compilationHelper
.addSourceLines(
"Foo.java",
"@javax.annotation.CheckReturnValue",
"public class Foo {",
" public int f() {",
" return 42;",
" }",
"}")
.addSourceLines(
"Test.java",
"import static com.google.common.truth.Truth.assert_;",
"class Test {",
" void f(Foo foo) {",
" try {",
" foo.f();",
" assert_().fail();",
" } catch (Exception expected) {}",
" }",
"}")
.doTest();
}
@Test
public void onlyIgnoreWithEnclosingTryCatch() throws Exception {
compilationHelper
.addSourceLines(
"Foo.java",
"@javax.annotation.CheckReturnValue",
"public class Foo {",
" public int f() {",
" return 42;",
" }",
"}")
.addSourceLines(
"Test.java",
"import static org.junit.Assert.fail;",
"class Test {",
" void f(Foo foo) {",
" // BUG: Diagnostic contains: Ignored return value",
" foo.f();",
" org.junit.Assert.fail();",
" // BUG: Diagnostic contains: Ignored return value",
" foo.f();",
" junit.framework.Assert.fail();",
" // BUG: Diagnostic contains: Ignored return value",
" foo.f();",
" junit.framework.TestCase.fail();",
" }",
"}")
.doTest();
}
@Test
public void ignoreInOrderVerification() throws Exception {
compilationHelper
.addSourceLines(
"Lib.java",
"public class Lib {",
" @javax.annotation.CheckReturnValue",
" public int f() {",
" return 0;",
" }",
"}")
.addSourceLines(
"Test.java",
"import static org.mockito.Mockito.inOrder;",
"class Test {",
" void m() {",
" inOrder().verify(new Lib()).f();",
" }",
"}")
.doTest();
}
@Rule public final TemporaryFolder tempFolder = new TemporaryFolder();
/** Test class containing a method annotated with @CRV. */
public static class CRVTest {
@javax.annotation.CheckReturnValue
public static int f() {
return 42;
}
}
static void addClassToJar(JarOutputStream jos, Class<?> clazz) throws IOException {
String entryPath = clazz.getName().replace('.', '/') + ".class";
try (InputStream is = clazz.getClassLoader().getResourceAsStream(entryPath)) {
jos.putNextEntry(new JarEntry(entryPath));
ByteStreams.copy(is, jos);
}
}
@Test
public void noCRVonClasspath() throws Exception {
File libJar = tempFolder.newFile("lib.jar");
try (FileOutputStream fis = new FileOutputStream(libJar);
JarOutputStream jos = new JarOutputStream(fis)) {
addClassToJar(jos, CRVTest.class);
addClassToJar(jos, CheckReturnValueTest.class);
}
compilationHelper
.addSourceLines(
"Test.java",
"class Test {",
" void m() {",
" // BUG: Diagnostic contains: Ignored return value",
" com.google.errorprone.bugpatterns.CheckReturnValueTest.CRVTest.f();",
" }",
"}")
.setArgs(Arrays.asList("-cp", libJar.toString()))
.doTest();
}
}