View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.coverage.lines;
7   
8   import edu.umd.cs.findbugs.annotations.NonNull;
9   import edu.umd.cs.findbugs.annotations.Nullable;
10  
11  import java.io.IOException;
12  import java.io.ObjectInputStream;
13  import java.util.HashMap;
14  import java.util.Map;
15  import java.util.Map.Entry;
16  
17  import mockit.coverage.CallPoint;
18  import mockit.coverage.CoveragePercentage;
19  import mockit.coverage.data.PerFileCoverage;
20  
21  import org.checkerframework.checker.index.qual.NonNegative;
22  
23  public final class PerFileLineCoverage implements PerFileCoverage {
24      private static final long serialVersionUID = 6318915843739466316L;
25      private static final int[] NO_EXECUTIONS_YET = {};
26  
27      @NonNull
28      private final Map<Integer, LineCoverageData> lineToLineData = new HashMap<>(128);
29  
30      @NonNull
31      private int[] executionCounts = NO_EXECUTIONS_YET;
32  
33      @NonNull
34      private transient LineCoverageData sharedLineData;
35  
36      // Computed on demand:
37      @NonNegative
38      private int lastLine;
39      private transient int totalSegments;
40      private transient int coveredSegments;
41  
42      public PerFileLineCoverage() {
43          sharedLineData = new LineCoverageData();
44          initializeCache();
45      }
46  
47      private void initializeCache() {
48          totalSegments = coveredSegments = -1;
49      }
50  
51      private void readObject(@NonNull ObjectInputStream in) throws IOException, ClassNotFoundException {
52          sharedLineData = new LineCoverageData();
53          initializeCache();
54          in.defaultReadObject();
55  
56          if (executionCounts.length == 0) {
57              executionCounts = NO_EXECUTIONS_YET;
58          }
59      }
60  
61      public void addLine(@NonNegative int line) {
62          if (!lineToLineData.containsKey(line)) {
63              lineToLineData.put(line, null);
64          }
65  
66          if (line > lastLine) {
67              // Happens for source files with multiple types, where one is only loaded
68              // after another has already executed some code.
69              int[] initialExecutionCounts = executionCounts;
70  
71              if (initialExecutionCounts != NO_EXECUTIONS_YET && line >= initialExecutionCounts.length) {
72                  int[] newCounts = new int[line + 30];
73                  System.arraycopy(initialExecutionCounts, 0, newCounts, 0, initialExecutionCounts.length);
74                  executionCounts = newCounts;
75              }
76  
77              lastLine = line;
78          }
79      }
80  
81      @NonNull
82      public LineCoverageData getOrCreateLineData(@NonNegative int line) {
83          LineCoverageData lineData = lineToLineData.get(line);
84  
85          if (lineData == null) {
86              lineData = new LineCoverageData();
87              lineToLineData.put(line, lineData);
88          }
89  
90          return lineData;
91      }
92  
93      @NonNull
94      public BranchCoverageData getBranchData(@NonNegative int line, @NonNegative int index) {
95          LineCoverageData lineData = lineToLineData.get(line);
96          return lineData.getBranchData(index);
97      }
98  
99      public void markLastLineSegmentAsEmpty(@NonNegative int line) {
100         LineCoverageData lineData = lineToLineData.get(line);
101         lineData.markLastSegmentAsEmpty();
102     }
103 
104     public boolean acceptsAdditionalCallPoints(@NonNegative int line) {
105         LineCoverageData lineData = getOrCreateLineData(line);
106         return lineData.acceptsAdditionalCallPoints();
107     }
108 
109     @NonNegative
110     public int registerExecution(@NonNegative int line, @Nullable CallPoint callPoint) {
111         if (executionCounts == NO_EXECUTIONS_YET) {
112             executionCounts = new int[lastLine + 1];
113         }
114 
115         int previousExecutionCount = executionCounts[line]++;
116 
117         if (callPoint != null) {
118             LineCoverageData lineData = lineToLineData.get(line);
119             lineData.registerExecution(callPoint);
120         }
121 
122         return previousExecutionCount;
123     }
124 
125     public boolean hasValidBranch(@NonNegative int line, @NonNegative int branchIndex) {
126         LineCoverageData lineData = lineToLineData.get(line);
127         return lineData.isValidBranch(branchIndex);
128     }
129 
130     public boolean acceptsAdditionalCallPoints(@NonNegative int line, @NonNegative int branchIndex) {
131         LineCoverageData lineData = lineToLineData.get(line);
132         return lineData.acceptsAdditionalCallPoints(branchIndex);
133     }
134 
135     @NonNegative
136     public int registerExecution(@NonNegative int line, @NonNegative int branchIndex, @Nullable CallPoint callPoint) {
137         LineCoverageData lineData = lineToLineData.get(line);
138         return lineData.registerExecution(branchIndex, callPoint);
139     }
140 
141     @NonNegative
142     public int getLineCount() {
143         return lastLine;
144     }
145 
146     @NonNegative
147     public int getExecutableLineCount() {
148         return lineToLineData.size();
149     }
150 
151     public boolean hasLineData(@NonNegative int line) {
152         return executionCounts != NO_EXECUTIONS_YET && lineToLineData.containsKey(line);
153     }
154 
155     @NonNull
156     public LineCoverageData getLineData(@NonNegative int line) {
157         LineCoverageData data = lineToLineData.get(line);
158 
159         if (data == null) {
160             data = sharedLineData;
161         }
162 
163         data.setExecutionCount(executionCounts[line]);
164         return data;
165     }
166 
167     public void markLineAsReachable(@NonNegative int line) {
168         LineCoverageData data = lineToLineData.get(line);
169 
170         if (data != null) {
171             data.markAsReachable();
172         }
173     }
174 
175     public int getExecutionCount(@NonNegative int line) {
176         return line < executionCounts.length ? executionCounts[line] : -1;
177     }
178 
179     @Override
180     @NonNegative
181     public int getTotalItems() {
182         computeValuesIfNeeded();
183         return totalSegments;
184     }
185 
186     @Override
187     @NonNegative
188     public int getCoveredItems() {
189         computeValuesIfNeeded();
190         return coveredSegments;
191     }
192 
193     @Override
194     public int getCoveragePercentage() {
195         computeValuesIfNeeded();
196         return CoveragePercentage.calculate(coveredSegments, totalSegments);
197     }
198 
199     private void computeValuesIfNeeded() {
200         if (totalSegments >= 0) {
201             return;
202         }
203         totalSegments = coveredSegments = 0;
204 
205         for (int line = 1, n = lastLine; line <= n; line++) {
206             if (lineToLineData.containsKey(line)) {
207                 LineCoverageData lineData = lineToLineData.get(line);
208                 int executionCount = executionCounts == NO_EXECUTIONS_YET ? 0 : executionCounts[line];
209 
210                 if (lineData == null) {
211                     totalSegments++;
212 
213                     if (executionCount > 0) {
214                         coveredSegments++;
215                     }
216                 } else {
217                     lineData.setExecutionCount(executionCount);
218                     totalSegments += lineData.getNumberOfSegments();
219                     coveredSegments += lineData.getNumberOfCoveredSegments();
220                 }
221             }
222         }
223     }
224 
225     @NonNegative
226     public int getNumberOfSegments(@NonNegative int line) {
227         if (!lineToLineData.containsKey(line)) {
228             return 0;
229         }
230 
231         LineCoverageData lineData = lineToLineData.get(line);
232         return lineData == null ? 1 : lineData.getNumberOfSegments();
233     }
234 
235     @NonNegative
236     public int getNumberOfBranchingSourcesAndTargets(@NonNegative int line) {
237         LineCoverageData lineData = lineToLineData.get(line);
238 
239         if (lineData == null) {
240             return 0;
241         }
242 
243         return lineData.getNumberOfBranchingSourcesAndTargets();
244     }
245 
246     public void mergeInformation(@NonNull PerFileLineCoverage previousCoverage) {
247         Map<Integer, LineCoverageData> previousInfo = previousCoverage.lineToLineData;
248         boolean previousRunHadLinesExecuted = previousCoverage.executionCounts.length > 0;
249 
250         for (Entry<Integer, LineCoverageData> lineAndInfo : lineToLineData.entrySet()) {
251             Integer line = lineAndInfo.getKey();
252             LineCoverageData previousLineInfo = previousInfo.get(line);
253 
254             if (previousLineInfo != null) {
255                 LineCoverageData lineInfo = lineAndInfo.getValue();
256 
257                 if (lineInfo == null) {
258                     lineInfo = new LineCoverageData();
259                     lineAndInfo.setValue(lineInfo);
260                 }
261 
262                 lineInfo.addCountsFromPreviousTestRun(previousLineInfo);
263 
264                 if (previousRunHadLinesExecuted) {
265                     createExecutionCountsArrayIfNeeded(previousCoverage);
266                     executionCounts[line] += previousCoverage.executionCounts[line];
267                 }
268             }
269         }
270 
271         for (Entry<Integer, LineCoverageData> lineAndInfo : previousInfo.entrySet()) {
272             Integer line = lineAndInfo.getKey();
273 
274             if (!lineToLineData.containsKey(line)) {
275                 LineCoverageData previousLineInfo = lineAndInfo.getValue();
276                 lineToLineData.put(line, previousLineInfo);
277 
278                 if (previousRunHadLinesExecuted) {
279                     createExecutionCountsArrayIfNeeded(previousCoverage);
280                     executionCounts[line] = previousCoverage.executionCounts[line];
281                 }
282             }
283         }
284     }
285 
286     private void createExecutionCountsArrayIfNeeded(@NonNull PerFileLineCoverage previousCoverage) {
287         if (executionCounts == NO_EXECUTIONS_YET) {
288             executionCounts = new int[previousCoverage.executionCounts.length];
289         }
290     }
291 }