1
2
3
4
5
6 package mockit.coverage;
7
8 import static java.lang.reflect.Modifier.isPublic;
9
10 import edu.umd.cs.findbugs.annotations.NonNull;
11 import edu.umd.cs.findbugs.annotations.Nullable;
12
13 import java.io.Serializable;
14 import java.lang.annotation.Annotation;
15 import java.lang.reflect.Method;
16 import java.util.HashMap;
17 import java.util.Map;
18
19 import mockit.internal.util.StackTrace;
20
21 import org.checkerframework.checker.index.qual.NonNegative;
22
23 public final class CallPoint implements Serializable {
24 private static final long serialVersionUID = 362727169057343840L;
25 private static final Map<StackTraceElement, Boolean> steCache = new HashMap<>();
26 private static final Class<? extends Annotation> testAnnotation;
27 private static final boolean checkTestAnnotationOnClass;
28 private static final boolean checkIfTestCaseSubclass;
29
30 static {
31 Class<?> annotation = getJUnitAnnotationIfAvailable();
32 boolean checkOnClassAlso = false;
33
34 if (annotation == null) {
35 annotation = getTestNGAnnotationIfAvailable();
36 checkOnClassAlso = true;
37 }
38
39
40 testAnnotation = (Class<? extends Annotation>) annotation;
41 checkTestAnnotationOnClass = checkOnClassAlso;
42 checkIfTestCaseSubclass = checkForJUnit3Availability();
43 }
44
45 @Nullable
46 private static Class<?> getJUnitAnnotationIfAvailable() {
47 try {
48
49 return Class.forName("org.junit.jupiter.api.Test");
50 } catch (ClassNotFoundException ignore) {
51
52 try {
53 return Class.forName("org.junit.Test");
54 } catch (ClassNotFoundException ignored) {
55 return null;
56 }
57 }
58 }
59
60 @Nullable
61 private static Class<?> getTestNGAnnotationIfAvailable() {
62 try {
63 return Class.forName("org.testng.annotations.Test");
64 } catch (ClassNotFoundException ignore) {
65
66 try {
67 return Class.forName("org.testng.Test");
68 } catch (ClassNotFoundException ignored) {
69 return null;
70 }
71 }
72 }
73
74 private static boolean checkForJUnit3Availability() {
75 try {
76 Class.forName("junit.framework.TestCase");
77 return true;
78 } catch (ClassNotFoundException ignore) {
79 return false;
80 }
81 }
82
83 @NonNull
84 private final StackTraceElement ste;
85 @NonNegative
86 private int repetitionCount;
87
88 private CallPoint(@NonNull StackTraceElement ste) {
89 this.ste = ste;
90 }
91
92 @NonNull
93 public StackTraceElement getStackTraceElement() {
94 return ste;
95 }
96
97 @NonNegative
98 public int getRepetitionCount() {
99 return repetitionCount;
100 }
101
102 public void incrementRepetitionCount() {
103 repetitionCount++;
104 }
105
106 public boolean isSameTestMethod(@NonNull CallPoint other) {
107 StackTraceElement thisSTE = ste;
108 StackTraceElement otherSTE = other.ste;
109 return thisSTE == otherSTE || thisSTE.getClassName().equals(otherSTE.getClassName())
110 && thisSTE.getMethodName().equals(otherSTE.getMethodName());
111 }
112
113 public boolean isSameLineInTestCode(@NonNull CallPoint other) {
114 return isSameTestMethod(other) && ste.getLineNumber() == other.ste.getLineNumber();
115 }
116
117 @Nullable
118 static CallPoint create(@NonNull Throwable newThrowable) {
119 StackTrace st = new StackTrace(newThrowable);
120 int n = st.getDepth();
121
122 for (int i = 2; i < n; i++) {
123 StackTraceElement ste = st.getElement(i);
124
125 if (isTestMethod(ste)) {
126 return new CallPoint(ste);
127 }
128 }
129
130 return null;
131 }
132
133 private static boolean isTestMethod(@NonNull StackTraceElement ste) {
134 if (steCache.containsKey(ste)) {
135 return steCache.get(ste);
136 }
137
138 boolean isTestMethod = false;
139
140 if (ste.getFileName() != null && ste.getLineNumber() >= 0) {
141 String className = ste.getClassName();
142
143 if (!isClassInExcludedPackage(className)) {
144 Class<?> aClass = loadClass(className);
145
146 if (aClass != null) {
147 isTestMethod = isTestMethod(aClass, ste.getMethodName());
148 }
149 }
150 }
151
152 steCache.put(ste, isTestMethod);
153 return isTestMethod;
154 }
155
156 private static boolean isClassInExcludedPackage(@NonNull String className) {
157 return className.startsWith("jakarta.") || className.startsWith("java.") || className.startsWith("javax.")
158 || className.startsWith("sun.") || className.startsWith("org.junit.")
159 || className.startsWith("org.testng.") || className.startsWith("mockit.");
160 }
161
162 @Nullable
163 private static Class<?> loadClass(@NonNull String className) {
164 try {
165 return Class.forName(className);
166 } catch (ClassNotFoundException | LinkageError ignore) {
167 return null;
168 }
169 }
170
171 private static boolean isTestMethod(@NonNull Class<?> testClass, @NonNull String methodName) {
172 if (checkTestAnnotationOnClass && testClass.isAnnotationPresent(testAnnotation)) {
173 return true;
174 }
175
176 Method method = findMethod(testClass, methodName);
177
178 return method != null && (containsATestFrameworkAnnotation(method.getDeclaredAnnotations())
179 || checkIfTestCaseSubclass && isJUnit3xTestMethod(testClass, method));
180 }
181
182 @Nullable
183 private static Method findMethod(@NonNull Class<?> aClass, @NonNull String name) {
184 try {
185 for (Method method : aClass.getDeclaredMethods()) {
186 if (method.getReturnType() == void.class && name.equals(method.getName())) {
187 return method;
188 }
189 }
190 } catch (NoClassDefFoundError ignore) {
191 }
192
193 return null;
194 }
195
196 private static boolean containsATestFrameworkAnnotation(@NonNull Annotation[] methodAnnotations) {
197 for (Annotation annotation : methodAnnotations) {
198 String annotationName = annotation.annotationType().getName();
199
200 if (annotationName.startsWith("org.junit.") || annotationName.startsWith("org.testng.")) {
201 return true;
202 }
203 }
204
205 return false;
206 }
207
208 private static boolean isJUnit3xTestMethod(@NonNull Class<?> aClass, @NonNull Method method) {
209 if (!isPublic(method.getModifiers()) || !method.getName().startsWith("test")) {
210 return false;
211 }
212
213 Class<?> superClass = aClass.getSuperclass();
214
215 while (superClass != Object.class) {
216 if ("junit.framework.TestCase".equals(superClass.getName())) {
217 return true;
218 }
219
220 superClass = superClass.getSuperclass();
221 }
222
223 return false;
224 }
225 }