1
2
3
4
5
6 package mockit.coverage.data;
7
8 import edu.umd.cs.findbugs.annotations.NonNull;
9 import edu.umd.cs.findbugs.annotations.Nullable;
10
11 import java.io.BufferedInputStream;
12 import java.io.BufferedOutputStream;
13 import java.io.File;
14 import java.io.IOException;
15 import java.io.ObjectInputStream;
16 import java.io.ObjectOutputStream;
17 import java.io.OutputStream;
18 import java.io.Serializable;
19 import java.nio.file.Files;
20 import java.nio.file.Path;
21 import java.util.ArrayList;
22 import java.util.Iterator;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.jar.JarEntry;
28 import java.util.jar.JarFile;
29
30 import mockit.coverage.CoveragePercentage;
31 import mockit.internal.util.Utilities;
32
33 import org.checkerframework.checker.index.qual.NonNegative;
34
35
36
37
38 public final class CoverageData implements Serializable {
39 private static final long serialVersionUID = -4860004226098360259L;
40 @NonNull
41 private static final CoverageData instance = new CoverageData();
42
43 @NonNull
44 public static CoverageData instance() {
45 return instance;
46 }
47
48 private boolean withCallPoints;
49
50 @NonNull
51 private final Map<String, FileCoverageData> fileToFileData = new LinkedHashMap<>();
52 @NonNull
53 private final List<FileCoverageData> indexedFileData = new ArrayList<>(100);
54
55 public boolean isWithCallPoints() {
56 return withCallPoints;
57 }
58
59 public void setWithCallPoints(boolean withCallPoints) {
60 this.withCallPoints = withCallPoints;
61 }
62
63 @NonNull
64 public Map<String, FileCoverageData> getFileToFileData() {
65 return fileToFileData;
66 }
67
68 @NonNull
69 public FileCoverageData getOrAddFile(@NonNull String file, @Nullable String kindOfTopLevelType) {
70 FileCoverageData fileData = fileToFileData.get(file);
71
72
73 if (fileData == null) {
74 int fileIndex = indexedFileData.size();
75 fileData = new FileCoverageData(fileIndex, kindOfTopLevelType);
76 indexedFileData.add(fileData);
77 fileToFileData.put(file, fileData);
78 } else if (kindOfTopLevelType != null) {
79 fileData.kindOfTopLevelType = kindOfTopLevelType;
80 }
81
82 return fileData;
83 }
84
85 @NonNull
86 public FileCoverageData getFileData(@NonNull String file) {
87 return fileToFileData.get(file);
88 }
89
90 @NonNull
91 public FileCoverageData getFileData(@NonNegative int fileIndex) {
92 return indexedFileData.get(fileIndex);
93 }
94
95 public boolean isEmpty() {
96 return fileToFileData.isEmpty();
97 }
98
99 public void clear() {
100 fileToFileData.clear();
101 }
102
103
104
105
106
107
108
109
110
111
112
113 public int getPercentage(@Nullable String fileNamePrefix) {
114 int coveredItems = 0;
115 int totalItems = 0;
116
117 for (Entry<String, FileCoverageData> fileAndFileData : fileToFileData.entrySet()) {
118 String sourceFile = fileAndFileData.getKey();
119
120 if (fileNamePrefix == null || sourceFile.startsWith(fileNamePrefix)) {
121 FileCoverageData fileData = fileAndFileData.getValue();
122 coveredItems += fileData.getCoveredItems();
123 totalItems += fileData.getTotalItems();
124 }
125 }
126
127 return CoveragePercentage.calculate(coveredItems, totalItems);
128 }
129
130
131
132
133
134
135
136 @NonNegative
137 public int getSmallestPerFilePercentage() {
138 int minPercentage = Integer.MAX_VALUE;
139
140 for (FileCoverageData fileData : fileToFileData.values()) {
141 if (!fileData.wasLoadedAfterTestCompletion()) {
142 int percentage = fileData.getCoveragePercentage();
143
144 if (percentage >= 0 && percentage < minPercentage) {
145 minPercentage = percentage;
146 }
147 }
148 }
149
150 return minPercentage;
151 }
152
153 public void fillLastModifiedTimesForAllClassFiles() {
154 for (Iterator<Entry<String, FileCoverageData>> itr = fileToFileData.entrySet().iterator(); itr.hasNext();) {
155 Entry<String, FileCoverageData> fileAndFileData = itr.next();
156 long lastModified = getLastModifiedTimeForClassFile(fileAndFileData.getKey());
157
158 if (lastModified > 0L) {
159 FileCoverageData fileCoverageData = fileAndFileData.getValue();
160 fileCoverageData.lastModified = lastModified;
161 continue;
162 }
163
164 itr.remove();
165 }
166 }
167
168 private long getLastModifiedTimeForClassFile(@NonNull String sourceFilePath) {
169 String sourceFilePathNoExt = sourceFilePath.substring(0, sourceFilePath.lastIndexOf('.'));
170 String className = sourceFilePathNoExt.replace('/', '.');
171
172 Class<?> coveredClass = findCoveredClass(className);
173
174 if (coveredClass == null) {
175 return 0L;
176 }
177
178 String locationPath = Utilities.getClassFileLocationPath(coveredClass);
179
180 if (locationPath.endsWith(".jar")) {
181 try {
182 return getLastModifiedTimeFromJarEntry(sourceFilePathNoExt, locationPath);
183 } catch (IOException ignore) {
184 return 0L;
185 }
186 }
187
188 String pathToClassFile = locationPath + sourceFilePathNoExt + ".class";
189
190 return Path.of(pathToClassFile).toFile().lastModified();
191 }
192
193 private static long getLastModifiedTimeFromJarEntry(@NonNull String sourceFilePathNoExt,
194 @NonNull String locationPath) throws IOException {
195
196 try (JarFile jarFile = new JarFile(locationPath)) {
197 JarEntry classEntry = jarFile.getJarEntry(sourceFilePathNoExt + ".class");
198 return classEntry.getTime();
199 }
200 }
201
202 @Nullable
203 private Class<?> findCoveredClass(@NonNull String className) {
204 ClassLoader currentCL = getClass().getClassLoader();
205 Class<?> coveredClass = loadClass(className, currentCL);
206
207 if (coveredClass == null) {
208 ClassLoader systemCL = ClassLoader.getSystemClassLoader();
209
210 if (systemCL != currentCL) {
211 coveredClass = loadClass(className, systemCL);
212 }
213
214 if (coveredClass == null) {
215 ClassLoader contextCL = Thread.currentThread().getContextClassLoader();
216
217 if (contextCL != null && contextCL != systemCL) {
218 coveredClass = loadClass(className, contextCL);
219 }
220 }
221 }
222
223 return coveredClass;
224 }
225
226 @Nullable
227 private static Class<?> loadClass(@NonNull String className, @Nullable ClassLoader loader) {
228 try {
229 return Class.forName(className, false, loader);
230 } catch (ClassNotFoundException | NoClassDefFoundError ignore) {
231 return null;
232 }
233 }
234
235
236
237
238
239
240
241
242
243
244 @NonNull
245 public static CoverageData readDataFromFile(@NonNull File dataFile) throws IOException {
246 try (ObjectInputStream input = new ObjectInputStream(
247 new BufferedInputStream(Files.newInputStream(dataFile.toPath())))) {
248 return (CoverageData) input.readObject();
249 } catch (ClassNotFoundException e) {
250 throw new RuntimeException(
251 "Serialized class in coverage data file \"" + dataFile + "\" not found in classpath", e);
252 }
253 }
254
255 public void writeDataToFile(@NonNull File dataFile) throws IOException {
256 try (OutputStream outputStream = Files.newOutputStream(dataFile.toPath());
257 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
258 ObjectOutputStream output = new ObjectOutputStream(bufferedOutputStream)) {
259 output.writeObject(this);
260 }
261 }
262
263 public void merge(@NonNull CoverageData previousData) {
264 withCallPoints |= previousData.withCallPoints;
265
266 for (Entry<String, FileCoverageData> previousFileAndFileData : previousData.fileToFileData.entrySet()) {
267 String previousFile = previousFileAndFileData.getKey();
268 FileCoverageData previousFileData = previousFileAndFileData.getValue();
269 FileCoverageData fileData = fileToFileData.get(previousFile);
270
271 if (fileData == null) {
272 fileToFileData.put(previousFile, previousFileData);
273 } else if (fileData.lastModified > 0 && previousFileData.lastModified == fileData.lastModified) {
274 fileData.mergeWithDataFromPreviousTestRun(previousFileData);
275 }
276 }
277 }
278 }