Files
codeql/java/ql/lib/semmle/code/java/UnitTests.qll
2025-03-20 09:22:13 +01:00

364 lines
10 KiB
Plaintext

/**
* Provides classes and predicates for working with test classes and methods.
*/
import Type
import Member
import semmle.code.java.frameworks.JUnitAnnotations
/** The Java class `junit.framework.TestCase`. */
class TypeJUnitTestCase extends RefType {
TypeJUnitTestCase() { this.hasQualifiedName("junit.framework", "TestCase") }
}
/** The Java interface `junit.framework.Test`. */
class TypeJUnitTest extends RefType {
TypeJUnitTest() { this.hasQualifiedName("junit.framework", "Test") }
}
/** The Java class `junit.framework.TestSuite`. */
class TypeJUnitTestSuite extends RefType {
TypeJUnitTestSuite() { this.hasQualifiedName("junit.framework", "TestSuite") }
}
/** A JUnit 3.8 test class. */
class JUnit38TestClass extends Class {
JUnit38TestClass() { exists(TypeJUnitTestCase tc | this.hasSupertype+(tc)) }
}
/** A JUnit 3.8 `tearDown` method. */
class TearDownMethod extends Method {
TearDownMethod() {
this.hasName("tearDown") and
this.hasNoParameters() and
this.getReturnType().hasName("void") and
exists(Method m | m.getDeclaringType() instanceof TypeJUnitTestCase | this.overrides*(m))
}
}
private class TestRelatedAnnotation extends Annotation {
TestRelatedAnnotation() {
this.getType()
.getPackage()
.hasName([
"org.testng.annotations", "org.junit", "org.junit.runner", "org.junit.jupiter.api",
"org.junit.jupiter.params"
])
}
}
private class TestRelatedMethod extends Method {
TestRelatedMethod() { this.getAnAnnotation() instanceof TestRelatedAnnotation }
}
/**
* A class detected to be a test class, either because it or one of its super-types
* and/or enclosing types contains a test method or method with a unit-test-related
* annotation.
*/
class TestClass extends Class {
TestClass() {
this instanceof JUnit38TestClass or
exists(TestMethod m | m.getDeclaringType() = this) or
exists(TestRelatedMethod m | m.getDeclaringType() = this) or
this.getASourceSupertype() instanceof TestClass or
this.getEnclosingType() instanceof TestClass
}
}
/**
* A class that is likely a test class. That is either a definite test class, or
* a class whose name, package, or location suggests that it might be a test class.
*/
class LikelyTestClass extends Class {
LikelyTestClass() {
this instanceof TestClass or
this.getName().toLowerCase().matches("%test%") or
this.getPackage().getName().toLowerCase().matches("%test%") or
this.getLocation().getFile().getAbsolutePath().matches("%/src/test/java%")
}
}
/**
* A test method declared within a JUnit 3.8 test class.
*/
class JUnit3TestMethod extends Method {
JUnit3TestMethod() {
this.isPublic() and
this.getDeclaringType() instanceof JUnit38TestClass and
this.getName().matches("test%") and
this.getReturnType().hasName("void") and
this.hasNoParameters()
}
}
/**
* A JUnit 3.8 test suite method.
*/
class JUnit3TestSuite extends Method {
JUnit3TestSuite() {
this.isPublic() and
this.isStatic() and
(
this.getDeclaringType() instanceof JUnit38TestClass or
this.getDeclaringType().getAnAncestor() instanceof TypeJUnitTestSuite
) and
this.hasName("suite") and
this.getReturnType() instanceof TypeJUnitTest and
this.hasNoParameters()
}
}
/**
* A JUnit test method that is annotated with the `org.junit.Test` annotation.
*/
class JUnit4TestMethod extends Method {
JUnit4TestMethod() { this.getAnAnnotation().getType().hasQualifiedName("org.junit", "Test") }
}
/**
* A JUnit test method that is annotated with the `org.junit.jupiter.api.Test` annotation.
*/
class JUnitJupiterTestMethod extends Method {
JUnitJupiterTestMethod() {
this.getAnAnnotation().getType().hasQualifiedName("org.junit.jupiter.api", "Test")
}
}
/**
* A JUnit `@Ignore` annotation.
*/
class JUnitIgnoreAnnotation extends Annotation {
JUnitIgnoreAnnotation() { this.getType().hasQualifiedName("org.junit", "Ignore") }
}
/**
* A method which, directly or indirectly, is treated as ignored by JUnit due to a `@Ignore`
* annotation.
*/
class JUnitIgnoredMethod extends Method {
JUnitIgnoredMethod() {
this.getAnAnnotation() instanceof JUnitIgnoreAnnotation
or
exists(Class c | c = this.getDeclaringType() |
c.getAnAnnotation() instanceof JUnitIgnoreAnnotation
)
}
}
/**
* An annotation in TestNG.
*/
class TestNGAnnotation extends Annotation {
TestNGAnnotation() { this.getType().getPackage().hasName("org.testng.annotations") }
}
/**
* An annotation of type `org.test.ng.annotations.Test`.
*/
class TestNGTestAnnotation extends TestNGAnnotation {
TestNGTestAnnotation() { this.getType().hasName("Test") }
}
/**
* A TestNG test method, annotated with the `org.testng.annotations.Test` annotation.
*/
class TestNGTestMethod extends Method {
TestNGTestMethod() { this.getAnAnnotation() instanceof TestNGTestAnnotation }
/**
* Identify a possible `DataProvider` for this method, if the annotation includes a `dataProvider`
* value.
*/
TestNGDataProviderMethod getADataProvider() {
exists(TestNGTestAnnotation testAnnotation |
testAnnotation = this.getAnAnnotation() and
// The data provider must have the same name as the referenced data provider
result.getDataProviderName() = testAnnotation.getStringValue("dataProvider")
|
// Either the data provider should be on the current class, or a supertype
this.getDeclaringType().getAnAncestor() = result.getDeclaringType()
or
// Or the data provider class should be declared
result.getDeclaringType() = testAnnotation.getTypeValue("dataProviderClass")
)
}
}
/**
* Any method detected to be a test method of a common testing framework,
* including JUnit and TestNG.
*/
class TestMethod extends Method {
TestMethod() {
this instanceof JUnit3TestMethod or
this instanceof JUnit4TestMethod or
this instanceof JUnitJupiterTestMethod or
this instanceof TestNGTestMethod
}
}
/**
* A method that is likely a test method.
*/
class LikelyTestMethod extends Method {
LikelyTestMethod() {
this.getDeclaringType() instanceof LikelyTestClass
or
this instanceof TestMethod
or
this instanceof LikelyJunitTest
}
}
/**
* A `Method` that is public, has no parameters,
* has a "void" return type, AND either has a name that starts with "test" OR
* has an annotation that ends with "Test"
*/
class LikelyJunitTest extends Method {
LikelyJunitTest() {
this.isPublic() and
this.getReturnType().hasName("void") and
this.hasNoParameters() and
(
this.getName().matches("JUnit%") or
this.getName().matches("test%") or
this.getAnAnnotation().getType().getName().matches("%Test")
)
}
}
/**
* A TestNG annotation used to mark a method that runs "before".
*/
class TestNGBeforeAnnotation extends TestNGAnnotation {
TestNGBeforeAnnotation() { this.getType().getName().matches("Before%") }
}
/**
* A TestNG annotation used to mark a method that runs "after".
*/
class TestNGAfterAnnotation extends TestNGAnnotation {
TestNGAfterAnnotation() { this.getType().getName().matches("After%") }
}
/**
* An annotation of type `org.testng.annotations.DataProvider` which is applied to methods to mark
* them as data provider methods for TestNG.
*/
class TestNGDataProviderAnnotation extends TestNGAnnotation {
TestNGDataProviderAnnotation() { this.getType().hasName("DataProvider") }
}
/**
* An annotation of type `org.testng.annotations.Factory` which is applied to methods to mark
* them as factory methods for TestNG.
*/
class TestNGFactoryAnnotation extends TestNGAnnotation {
TestNGFactoryAnnotation() { this.getType().hasName("Factory") }
}
/**
* An annotation of type `org.testng.annotations.Listeners` which is applied to classes to define
* which listeners apply to them.
*/
class TestNGListenersAnnotation extends TestNGAnnotation {
TestNGListenersAnnotation() { this.getType().hasName("Listeners") }
/**
* Gets a listener defined in this annotation.
*/
TestNGListenerImpl getAListener() { result = this.getATypeArrayValue("value") }
}
/**
* A concrete implementation class of one or more of the TestNG listener interfaces.
*/
class TestNGListenerImpl extends Class {
TestNGListenerImpl() { this.getAnAncestor().hasQualifiedName("org.testng", "ITestNGListener") }
}
/**
* A method annotated with `org.testng.annotations.DataProvider` marking it as a data provider method
* for TestNG.
*
* This data provider method can be referenced by "name", and used by the test framework to provide
* an instance of a particular value when running a test method.
*/
class TestNGDataProviderMethod extends Method {
TestNGDataProviderMethod() { this.getAnAnnotation() instanceof TestNGDataProviderAnnotation }
/**
* Gets the name associated with this data provider.
*/
string getDataProviderName() {
result =
this.getAnAnnotation()
.(TestNGDataProviderAnnotation)
.getValue("name")
.(StringLiteral)
.getValue()
}
}
/**
* A constructor or method annotated with `org.testng.annotations.Factory` marking it as a factory
* for TestNG.
*
* This factory callable is used to generate instances of parameterized test classes.
*/
class TestNGFactoryCallable extends Callable {
TestNGFactoryCallable() { this.getAnAnnotation() instanceof TestNGFactoryAnnotation }
}
/**
* A class that will be run using the `org.junit.runners.Parameterized` JUnit runner.
*/
class ParameterizedJUnitTest extends Class {
ParameterizedJUnitTest() {
this.getAnAnnotation()
.(RunWithAnnotation)
.getRunner()
.(Class)
.hasQualifiedName("org.junit.runners", "Parameterized")
}
}
/**
* A `@Category` annotation on a class or method, that categorizes the annotated test.
*/
class JUnitCategoryAnnotation extends Annotation {
JUnitCategoryAnnotation() {
this.getType().hasQualifiedName("org.junit.experimental.categories", "Category")
}
/**
* One of the categories that this test is categorized as.
*/
Type getACategory() {
exists(TypeLiteral literal, Expr value |
value = this.getValue("value") and
(
literal = value or
literal = value.(ArrayCreationExpr).getInit().getAnInit()
)
|
result = literal.getReferencedType()
)
}
}
/**
* A test class that will be run with theories.
*/
class JUnitTheoryTest extends Class {
JUnitTheoryTest() {
this.getAnAnnotation()
.(RunWithAnnotation)
.getRunner()
.(Class)
.hasQualifiedName("org.junit.experimental.theories", "Theories")
}
}