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