-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Report unannotated JUnit Test annotation
- Loading branch information
Showing
17 changed files
with
265 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
...ng/trino-testing-services/src/main/java/io/trino/junit/ReportBadJunitTestAnnotations.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/* | ||
* 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 io.trino.junit; | ||
|
||
import com.google.common.annotations.VisibleForTesting; | ||
import io.trino.testng.services.ReportBadTestAnnotations; | ||
import org.junit.jupiter.api.extension.ExtensionContext; | ||
import org.junit.jupiter.api.extension.TestInstanceFactoryContext; | ||
import org.junit.jupiter.api.extension.TestInstancePreConstructCallback; | ||
|
||
import java.lang.annotation.Annotation; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Modifier; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import static com.google.common.base.Throwables.getStackTraceAsString; | ||
import static com.google.common.collect.ImmutableList.toImmutableList; | ||
import static io.trino.testing.Listeners.reportListenerFailure; | ||
import static java.util.stream.Collectors.joining; | ||
|
||
public class ReportBadJunitTestAnnotations | ||
implements TestInstancePreConstructCallback | ||
{ | ||
@Override | ||
public void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) | ||
{ | ||
Class<?> testClass = factoryContext.getTestClass(); | ||
try { | ||
reportBadTestAnnotations(testClass); | ||
} | ||
catch (RuntimeException | Error e) { | ||
reportListenerFailure( | ||
ReportBadTestAnnotations.class, | ||
"Failed to process %s: \n%s", | ||
testClass, | ||
getStackTraceAsString(e)); | ||
} | ||
} | ||
|
||
private void reportBadTestAnnotations(Class<?> testClass) | ||
{ | ||
List<Method> unannotatedTestMethods = findUnannotatedInheritedTestMethods(testClass); | ||
if (!unannotatedTestMethods.isEmpty()) { | ||
reportListenerFailure( | ||
ReportBadJunitTestAnnotations.class, | ||
"Test class %s has methods which are inherited but not explicitly annotated. Are they missing @Test?%s", | ||
testClass.getName(), | ||
unannotatedTestMethods.stream() | ||
.map(Method::toString) | ||
.collect(joining("\n\t\t", "\n\t\t", ""))); | ||
} | ||
} | ||
|
||
@VisibleForTesting | ||
static List<Method> findUnannotatedInheritedTestMethods(Class<?> realClass) | ||
{ | ||
return Arrays.stream(realClass.getMethods()) | ||
.filter(method -> method.getDeclaringClass() != Object.class) | ||
.filter(method -> !Modifier.isStatic(method.getModifiers())) | ||
.filter(method -> !method.isBridge()) | ||
.filter(method -> isUnannotated(method) && overriddenMethodHasTestAnnotation(method)) | ||
.collect(toImmutableList()); | ||
} | ||
|
||
private static boolean isUnannotated(Method method) | ||
{ | ||
return Arrays.stream(method.getAnnotations()).map(Annotation::annotationType) | ||
.noneMatch(ReportBadJunitTestAnnotations::isJUnitAnnotation); | ||
} | ||
|
||
private static boolean isJUnitAnnotation(Class<? extends Annotation> clazz) | ||
{ | ||
return clazz.getPackage().getName().startsWith("org.junit.jupiter.api"); | ||
} | ||
|
||
private static boolean overriddenMethodHasTestAnnotation(Method method) | ||
{ | ||
if (method.isAnnotationPresent(org.junit.jupiter.api.Test.class)) { | ||
return true; | ||
} | ||
|
||
// Skip methods in Object class, e.g. toString() | ||
if (method.getDeclaringClass() == Object.class) { | ||
return false; | ||
} | ||
|
||
// The test class may override the default method of the interface | ||
for (Class<?> interfaceClass : method.getDeclaringClass().getInterfaces()) { | ||
Optional<Method> overridden = getOverridden(method, interfaceClass); | ||
if (overridden.isPresent() && overridden.get().isAnnotationPresent(org.junit.jupiter.api.Test.class)) { | ||
return true; | ||
} | ||
} | ||
|
||
Class<?> superClass = method.getDeclaringClass().getSuperclass(); | ||
if (superClass == null) { | ||
return false; | ||
} | ||
return getOverridden(method, superClass) | ||
.map(ReportBadJunitTestAnnotations::overriddenMethodHasTestAnnotation) | ||
.orElse(false); | ||
} | ||
|
||
private static Optional<Method> getOverridden(Method method, Class<?> base) | ||
{ | ||
try { | ||
// Simplistic override detection | ||
return Optional.of(base.getMethod(method.getName(), method.getParameterTypes())); | ||
} | ||
catch (NoSuchMethodException ignored) { | ||
return Optional.empty(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
...g-services/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
io.trino.junit.ReportBadJunitTestAnnotations |
1 change: 1 addition & 0 deletions
1
testing/trino-testing-services/src/main/resources/junit-platform.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
junit.jupiter.extensions.autodetection.enabled=true |
113 changes: 113 additions & 0 deletions
113
...rino-testing-services/src/test/java/io/trino/junit/TestReportBadJunitTestAnnotations.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
* 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 io.trino.junit; | ||
|
||
import org.junit.jupiter.api.AfterAll; | ||
import org.junit.jupiter.api.BeforeAll; | ||
import org.junit.jupiter.api.Test; | ||
import org.testng.annotations.AfterClass; | ||
import org.testng.annotations.BeforeClass; | ||
|
||
import java.lang.reflect.Method; | ||
|
||
import static io.trino.junit.ReportBadJunitTestAnnotations.findUnannotatedInheritedTestMethods; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
public class TestReportBadJunitTestAnnotations | ||
{ | ||
@Test | ||
public void testTest() | ||
{ | ||
assertThat(findUnannotatedInheritedTestMethods(TestingTest.class)) | ||
.isEmpty(); | ||
assertThat(findUnannotatedInheritedTestMethods(TestingTestWithProxy.class)) | ||
.isEmpty(); | ||
assertThat(findUnannotatedInheritedTestMethods(TestingBeforeAfterAnnotations.class)) | ||
.isEmpty(); | ||
assertThat(findUnannotatedInheritedTestMethods(TestingTestWithoutTestAnnotation.class)) | ||
.isEmpty(); | ||
} | ||
|
||
@Test | ||
public void testTestWithoutTestAnnotation() | ||
{ | ||
assertThat(findUnannotatedInheritedTestMethods(TestingTestWithoutAnnotation.class)) | ||
.extracting(Method::getName) | ||
.containsExactly("testInInterface"); | ||
} | ||
|
||
private static class TestingTest | ||
implements TestingInterfaceWithTest | ||
{ | ||
@Test | ||
public void test() {} | ||
} | ||
|
||
private static class TestingTestWithoutAnnotation | ||
implements TestingInterfaceWithTest | ||
{ | ||
@Override | ||
public void testInInterface() | ||
{ | ||
TestingInterfaceWithTest.super.testInInterface(); | ||
} | ||
} | ||
|
||
private static class TestingTestWithProxy | ||
extends TestingInterfaceWithTestProxy | ||
{ | ||
@Test | ||
public void test() {} | ||
} | ||
|
||
private static class TestingTestWithoutTestAnnotation | ||
implements TestingInterface | ||
{ | ||
public void testWithMissingTestAnnotation() {} | ||
|
||
@Override | ||
public String toString() | ||
{ | ||
return "test override"; | ||
} | ||
} | ||
|
||
private static class TestingInterfaceWithTestProxy | ||
implements TestingInterfaceWithTest {} | ||
|
||
private interface TestingInterfaceWithTest | ||
{ | ||
@Test | ||
default void testInInterface() {} | ||
} | ||
|
||
private interface TestingInterface | ||
{ | ||
default void methodInInterface() {} | ||
} | ||
|
||
private static class TestingBeforeAfterAnnotations | ||
extends BaseTest {} | ||
|
||
private static class BaseTest | ||
{ | ||
@BeforeAll | ||
@BeforeClass | ||
public final void initialize() {} | ||
|
||
@AfterAll | ||
@AfterClass(alwaysRun = true) | ||
public final void destroy() {} | ||
} | ||
} |