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;
7   
8   import edu.umd.cs.findbugs.annotations.NonNull;
9   import edu.umd.cs.findbugs.annotations.Nullable;
10  
11  import java.io.File;
12  import java.io.IOException;
13  import java.nio.file.Path;
14  import java.util.ArrayList;
15  import java.util.List;
16  import java.util.regex.Pattern;
17  
18  import mockit.coverage.data.CoverageData;
19  
20  import org.checkerframework.checker.index.qual.NonNegative;
21  import org.slf4j.Logger;
22  import org.slf4j.LoggerFactory;
23  
24  final class CoverageCheck {
25  
26      /** The logger. */
27      private static final Logger logger = LoggerFactory.getLogger(CoverageCheck.class);
28  
29      private static final String configuration = Configuration.getProperty("check", "");
30  
31      @Nullable
32      static CoverageCheck createIfApplicable() {
33          return configuration.isEmpty() ? null : new CoverageCheck();
34      }
35  
36      private static final class Threshold {
37          private static final Pattern PARAMETER_SEPARATORS = Pattern.compile(":|=");
38  
39          @Nullable
40          private final String sourceFilePrefix;
41          @NonNull
42          private final String scopeDescription;
43          @NonNegative
44          private int minPercentage;
45  
46          Threshold(@NonNull String configurationParameter) {
47              String[] sourceFilePrefixAndMinPercentage = PARAMETER_SEPARATORS.split(configurationParameter);
48              String textualPercentage;
49  
50              if (sourceFilePrefixAndMinPercentage.length == 1) {
51                  sourceFilePrefix = null;
52                  scopeDescription = "";
53                  textualPercentage = sourceFilePrefixAndMinPercentage[0];
54              } else {
55                  String scope = sourceFilePrefixAndMinPercentage[0].trim();
56  
57                  if (isPerFile(scope)) {
58                      sourceFilePrefix = scope;
59                      scopeDescription = " for some source files";
60                  } else {
61                      sourceFilePrefix = scope.replace('.', '/');
62                      scopeDescription = " for " + scope;
63                  }
64  
65                  textualPercentage = sourceFilePrefixAndMinPercentage[1];
66              }
67  
68              try {
69                  minPercentage = Integer.parseInt(textualPercentage.trim());
70              } catch (NumberFormatException ignore) {
71              }
72          }
73  
74          private static boolean isPerFile(@Nullable String scope) {
75              return "perFile".equalsIgnoreCase(scope);
76          }
77  
78          boolean verifyMinimum() {
79              CoverageData coverageData = CoverageData.instance();
80              int percentage;
81  
82              if (isPerFile(sourceFilePrefix)) {
83                  percentage = coverageData.getSmallestPerFilePercentage();
84              } else {
85                  percentage = coverageData.getPercentage(sourceFilePrefix);
86              }
87  
88              return percentage < 0 || verifyMinimum(percentage);
89          }
90  
91          private boolean verifyMinimum(@NonNegative int percentage) {
92              if (percentage < minPercentage) {
93                  logger.info("JMockit: coverage too low {}: {}% < {}%", scopeDescription, percentage, minPercentage);
94                  return false;
95              }
96  
97              return true;
98          }
99      }
100 
101     @NonNull
102     private final List<Threshold> thresholds;
103     private boolean allThresholdsSatisfied;
104 
105     private CoverageCheck() {
106         String[] configurationParameters = configuration.split(";");
107         int n = configurationParameters.length;
108         thresholds = new ArrayList<>(n);
109 
110         for (String configurationParameter : configurationParameters) {
111             thresholds.add(new Threshold(configurationParameter));
112         }
113     }
114 
115     void verifyThresholds() {
116         allThresholdsSatisfied = true;
117 
118         for (Threshold threshold : thresholds) {
119             allThresholdsSatisfied &= threshold.verifyMinimum();
120         }
121 
122         createOrDeleteIndicatorFile();
123 
124         if (!allThresholdsSatisfied) {
125             throw new AssertionError("JMockit: minimum coverage percentages not reached; see previous messages.");
126         }
127     }
128 
129     @SuppressWarnings("ResultOfMethodCallIgnored")
130     private void createOrDeleteIndicatorFile() {
131         String parentDir = Configuration.getOrChooseOutputDirectory("");
132         File indicatorFile = Path.of(parentDir, "coverage.check.failed").toFile();
133 
134         if (indicatorFile.exists()) {
135             if (allThresholdsSatisfied) {
136                 indicatorFile.delete();
137             } else {
138                 indicatorFile.setLastModified(System.currentTimeMillis());
139             }
140         } else if (!allThresholdsSatisfied) {
141             try {
142                 indicatorFile.createNewFile();
143             } catch (IOException e) {
144                 throw new RuntimeException(e);
145             }
146         }
147     }
148 }