-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathUnitTests.qll
More file actions
416 lines (376 loc) · 11.8 KB
/
UnitTests.qll
File metadata and controls
416 lines (376 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
/**
* Provides classes and predicates for working with test classes and methods.
*/
overlay[local?]
module;
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 5 test method.
*
* A test method is defined by JUnit as "any instance method
* that is directly annotated or meta-annotated with `@Test`,
* `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`, or
* `@TestTemplate`."
*
* See https://junit.org/junit5/docs/current/user-guide/#writing-tests-definitions
*/
class JUnit5TestMethod extends Method {
JUnit5TestMethod() {
this instanceof JUnitJupiterTestMethod or
this.getAnAnnotation()
.getType()
.hasQualifiedName("org.junit.jupiter.api", ["RepeatedTest", "TestFactory", "TestTemplate"]) or
this.getAnAnnotation()
.getType()
.hasQualifiedName("org.junit.jupiter.params", "ParameterizedTest")
}
}
/**
* A JUnit 5 test class.
*
* A test class must contain at least one test method, and
* cannot be abstract.
*
* See https://junit.org/junit5/docs/current/user-guide/#writing-tests-definitions
*/
class JUnit5TestClass extends Class {
JUnit5TestClass() {
this.getAMethod() instanceof JUnit5TestMethod and
not this.isAbstract()
}
}
/**
* A JUnit inner test class that is non-anonymous, non-local,
* and non-private.
*/
class JUnit5InnerTestClass extends JUnit5TestClass {
JUnit5InnerTestClass() {
// `InnerClass` is a non-static nested class.
this instanceof InnerClass and
not this.isAnonymous() and
not this.isLocal() and
not this.isPrivate()
}
}
/**
* 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")
}
}