• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 644

06 Jul 2026 10:07AM UTC coverage: 79.186% (+0.05%) from 79.133%
644

push

github

web-flow
[java] Fix #6826: AssertEqualsArgumentOrder false positive for double/float delta (#6827)

The wildcard assertEquals(_,_,_) matchers for org.junit.Assert and
junit.framework.TestCase modeled the 3-arg form as (message?, expected,
actual), so for the delta overload assertEquals(double expected, double
actual, double delta) they read the delta as "actual" and flagged
correctly-ordered calls.

Replace the wildcards with the forms actually in use: (String,_,_) and
(String,_,_,_) for the message variants, (double,double,double) and
(float,float,float) for the deltas. The message + delta form was not
matched at all before, so a swap there went undetected.

Fixes pmd/pmd#6826.

Co-authored-by: Sören Glimm <git@uncleowen.de>

19178 of 25155 branches covered (76.24%)

Branch coverage included in aggregate %.

41598 of 51596 relevant lines covered (80.62%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.28
/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.cli.commands.internal;
6

7
import java.io.IOException;
8
import java.io.OutputStreamWriter;
9
import java.io.Writer;
10
import java.nio.charset.Charset;
11
import java.nio.file.Path;
12
import java.util.ArrayList;
13
import java.util.Iterator;
14
import java.util.List;
15
import java.util.Properties;
16
import java.util.stream.Collectors;
17

18
import org.checkerframework.checker.nullness.qual.NonNull;
19
import org.slf4j.Logger;
20
import org.slf4j.LoggerFactory;
21

22
import net.sourceforge.pmd.PMDConfiguration;
23
import net.sourceforge.pmd.PmdAnalysis;
24
import net.sourceforge.pmd.benchmark.TextTimingReportRenderer;
25
import net.sourceforge.pmd.benchmark.TimeTracker;
26
import net.sourceforge.pmd.benchmark.TimingReport;
27
import net.sourceforge.pmd.benchmark.TimingReportRenderer;
28
import net.sourceforge.pmd.cli.commands.typesupport.internal.NumThreadsConverter;
29
import net.sourceforge.pmd.cli.commands.typesupport.internal.PmdLanguageTypeSupport;
30
import net.sourceforge.pmd.cli.commands.typesupport.internal.PmdLanguageVersionTypeSupport;
31
import net.sourceforge.pmd.cli.commands.typesupport.internal.RulePriorityTypeSupport;
32
import net.sourceforge.pmd.cli.internal.CliExitCode;
33
import net.sourceforge.pmd.cli.internal.ProgressBarListener;
34
import net.sourceforge.pmd.internal.LogMessages;
35
import net.sourceforge.pmd.lang.Language;
36
import net.sourceforge.pmd.lang.LanguageVersion;
37
import net.sourceforge.pmd.lang.rule.RulePriority;
38
import net.sourceforge.pmd.properties.PropertyDescriptor;
39
import net.sourceforge.pmd.renderers.Renderer;
40
import net.sourceforge.pmd.renderers.RendererFactory;
41
import net.sourceforge.pmd.reporting.ReportStats;
42
import net.sourceforge.pmd.util.StringUtil;
43
import net.sourceforge.pmd.util.log.PmdReporter;
44
import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter;
45

46
import picocli.CommandLine;
47
import picocli.CommandLine.Command;
48
import picocli.CommandLine.Option;
49
import picocli.CommandLine.ParameterException;
50

51
@Command(name = "check", showDefaultValues = true,
52
    description = "The PMD standard source code analyzer")
53
public class PmdCommand extends AbstractAnalysisPmdSubcommand<PMDConfiguration> {
1✔
54
    private static final Logger LOG = LoggerFactory.getLogger(PmdCommand.class);
1✔
55

56
    static {
57
        final Properties emptyProps = new Properties();
1✔
58
        final StringBuilder reportPropertiesHelp = new StringBuilder();
1✔
59
        final String lineSeparator = System.lineSeparator();
1✔
60
        
61
        for (final String rendererName : RendererFactory.supportedRenderers()) {
1✔
62
            final Renderer renderer = RendererFactory.createRenderer(rendererName, emptyProps);
1✔
63
            
64
            if (!renderer.getPropertyDescriptors().isEmpty()) {
1✔
65
                reportPropertiesHelp.append(rendererName + ":" + lineSeparator);
1✔
66
                for (final PropertyDescriptor<?> property : renderer.getPropertyDescriptors()) {
1✔
67
                    reportPropertiesHelp.append("  ").append(property.name()).append(" - ")
1✔
68
                        .append(property.description()).append(lineSeparator);
1✔
69
                    final Object deflt = property.defaultValue();
1✔
70
                    if (deflt != null && !"".equals(deflt)) {
1!
71
                        reportPropertiesHelp.append("    Default: ").append(deflt)
1✔
72
                            .append(lineSeparator);
1✔
73
                    }
74
                }
1✔
75
            }
76
        }
1✔
77
        
78
        // System Properties are the easier way to inject dynamically computed values into the help of an option
79
        System.setProperty("pmd-cli.pmd.report.properties.help", reportPropertiesHelp.toString());
1✔
80
    }
1✔
81

82
    private List<String> rulesets;
83
    
84

85
    private String format;
86

87
    private int threads;
88

89
    private boolean benchmark;
90

91
    private boolean showSuppressed;
92

93
    private String suppressMarker;
94

95
    private RulePriority minimumPriority;
96

97
    private Properties properties = new Properties();
1✔
98

99
    private List<LanguageVersion> languageVersion;
100

101
    private Language forceLanguage;
102

103
    private String auxClasspath;
104

105
    private Path cacheLocation;
106

107
    private boolean noCache;
108

109
    private boolean showProgressBar;
110

111

112
    @CommandLine.ArgGroup(heading = FILE_COLLECTION_OPTION_HEADER, exclusive = false)
1✔
113
    FileCollectionOptions<PMDConfiguration> files = new FileCollectionOptions<>();
114

115
    @Option(names = { "--rulesets", "-R" },
116
               description = "Path to a ruleset xml file. "
117
                             + "The path may reference a resource on the classpath of the application, be a local file system path, or a URL. "
118
                             + "The option can be repeated, and multiple arguments separated by comma can be provided to a single occurrence of the option.",
119
               required = true, split = ",", arity = "1..*")
120
    public void setRulesets(final List<String> rulesets) {
121
        this.rulesets = rulesets;
1✔
122
    }
1✔
123

124

125
    @Option(names = { "--format", "-f" },
126
            description = "Report format.%nValid values: ${COMPLETION-CANDIDATES}%n"
127
                    + "Alternatively, you can provide the fully qualified name of a custom Renderer in the classpath.",
128
            defaultValue = "text", completionCandidates = PmdSupportedReportFormatsCandidates.class)
129
    public void setFormat(final String format) {
130
        this.format = format;
1✔
131
    }
1✔
132

133
    @Option(names = { "--benchmark", "-b" },
134
            description = "Benchmark mode - output a benchmark report upon completion; default to System.err.")
135
    public void setBenchmark(final boolean benchmark) {
136
        this.benchmark = benchmark;
×
137
    }
×
138

139
    @Option(names = "--show-suppressed", description = "Report should show suppressed rule violations if supported by the report format.")
140
    public void setShowSuppressed(final boolean showSuppressed) {
141
        this.showSuppressed = showSuppressed;
×
142
    }
×
143

144
    @Option(names = "--suppress-marker",
145
            description = "Specifies the string that marks a line which PMD should ignore.",
146
            defaultValue = "NOPMD")
147
    public void setSuppressMarker(final String suppressMarker) {
148
        this.suppressMarker = suppressMarker;
1✔
149
    }
1✔
150

151
    @Option(names = "--minimum-priority",
152
            description = "Rule priority threshold; rules with lower priority than configured here won't be used.%n"
153
                    + "Valid values (case insensitive): ${COMPLETION-CANDIDATES}",
154
            defaultValue = "Low",
155
            completionCandidates = RulePriorityTypeSupport.class, converter = RulePriorityTypeSupport.class)
156
    public void setMinimumPriority(final RulePriority priority) {
157
        this.minimumPriority = priority;
1✔
158
    }
1✔
159

160
    @Option(names = { "--property", "-P" }, description = "Key-value pair defining a property for the report format.%n"
161
                + "Supported values for each report format:%n${sys:pmd-cli.pmd.report.properties.help}",
162
            completionCandidates = PmdReportPropertiesCandidates.class)
163
    public void setProperties(final Properties properties) {
164
        this.properties = properties;
×
165
    }
×
166

167
    @Option(names = "--use-version",
168
            description = "The language version PMD should use when parsing source code.%nValid values: ${COMPLETION-CANDIDATES}",
169
            completionCandidates = PmdLanguageVersionTypeSupport.class, converter = PmdLanguageVersionTypeSupport.class)
170
    public void setLanguageVersion(final List<LanguageVersion> languageVersion) {
171
        // Make sure we only set 1 version per language
172
        languageVersion.stream().collect(Collectors.groupingBy(LanguageVersion::getLanguage))
1✔
173
            .forEach((l, list) -> {
1✔
174
                if (list.size() > 1) {
1!
175
                    throw new ParameterException(spec.commandLine(), "Can only set one version per language, "
×
176
                            + "but for language " + l.getName() + " multiple versions were provided "
×
177
                            + list.stream().map(LanguageVersion::getTerseName).collect(Collectors.joining("', '", "'", "'")));
×
178
                }
179
            });
1✔
180

181
        this.languageVersion = languageVersion;
1✔
182
    }
1✔
183

184
    @Option(names = "--force-language",
185
            description = "Force a language to be used for all input files, irrespective of file names. "
186
                          + "When using this option, the automatic language selection by extension is disabled, and PMD "
187
                          + "tries to parse all input files with the given language's parser. "
188
                          + "Parsing errors are ignored.%nValid values: ${COMPLETION-CANDIDATES}",
189
            completionCandidates = PmdLanguageTypeSupport.class, converter = PmdLanguageTypeSupport.class)
190
    public void setForceLanguage(final Language forceLanguage) {
191
        this.forceLanguage = forceLanguage;
1✔
192
    }
1✔
193

194
    @Option(names = "--aux-classpath",
195
            description = "Specifies the classpath for libraries used by the source code. "
196
                    + "This is used to resolve types in Java source files. The platform specific path delimiter "
197
                    + "(\":\" on Linux, \";\" on Windows) is used to separate the entries. "
198
                    + "Alternatively, a single 'file:' URL to a text file containing path elements on consecutive lines "
199
                    + "can be specified.")
200
    public void setAuxClasspath(final String auxClasspath) {
201
        this.auxClasspath = auxClasspath;
×
202
    }
×
203

204
    @Option(names = "--cache",
205
            description = "Specify the location of the cache file for incremental analysis. "
206
                    + "This should be the full path to the file, including the desired file name (not just the parent directory). "
207
                    + "If the file doesn't exist, it will be created on the first run. The file will be overwritten on each run "
208
                    + "with the most up-to-date rule violations.")
209
    public void setCacheLocation(final Path cacheLocation) {
210
        this.cacheLocation = cacheLocation;
×
211
    }
×
212

213
    @Option(names = "--no-cache", description = "Explicitly disable incremental analysis. The '-cache' option is ignored if this switch is present in the command line.")
214
    public void setNoCache(final boolean noCache) {
215
        this.noCache = noCache;
1✔
216
    }
1✔
217

218
    @Option(names = {"--threads", "-t"}, description =
219
        "Set the number of threads used by PMD. This can be an integer, or a float (or int) followed by the letter `C`, eg `0.5C` or `1C`. "
220
            + "In the latter case, the float will be multiplied by the number of cores of the host machine, and rounded down to an integer. "
221
            + "If the specified number of threads is zero, then PMD will use the main thread for everything. If it is `n` > 0, "
222
            + "PMD will spawn `n` separate analysis threads besides the main thread.",
223
        defaultValue = "1C", converter = NumThreadsConverter.class)
224
    public void setThreads(final int threads) {
225
        if (threads < 0) {
1!
226
            throw new ParameterException(spec.commandLine(), "Thread count should be a positive number or zero, found " + threads + " instead.");
×
227
        }
228
        
229
        this.threads = threads;
1✔
230
    }
1✔
231

232
    @Option(names = "--no-progress", negatable = true, defaultValue = "true",
233
            description = "Enables / disables progress bar indicator of live analysis progress.")
234
    public void setShowProgressBar(final boolean showProgressBar) {
235
        this.showProgressBar = showProgressBar;
1✔
236
    }
1✔
237

238

239
    @Override
240
    protected FileCollectionOptions<PMDConfiguration> getFileCollectionOptions() {
241
        return files;
1✔
242
    }
243

244
    /**
245
     * Converts these parameters into a configuration.
246
     *
247
     * @return A new PMDConfiguration corresponding to these parameters
248
     *
249
     * @throws ParameterException if the parameters are inconsistent or incomplete
250
     */
251
    @Override
252
    protected PMDConfiguration toConfiguration() {
253
        final PMDConfiguration configuration = new PMDConfiguration();
1✔
254
        setCommonConfigProperties(configuration);
1✔
255

256
        configuration.setReportFormat(format);
1✔
257
        configuration.setMinimumPriority(minimumPriority);
1✔
258
        configuration.setReportProperties(properties);
1✔
259
        configuration.setRuleSets(rulesets);
1✔
260
        configuration.setShowSuppressedViolations(showSuppressed);
1✔
261
        configuration.setSuppressMarker(suppressMarker);
1✔
262
        configuration.setAnalysisCacheLocation(cacheLocation != null ? cacheLocation.toString() : null);
1!
263
        configuration.setIgnoreIncrementalAnalysis(noCache);
1✔
264
        configuration.setThreads(threads);
1✔
265

266
        if (languageVersion != null) {
1✔
267
            configuration.setDefaultLanguageVersions(languageVersion);
1✔
268
        }
269
        
270
        // Important: do this after setting default versions, so we can pick them up
271
        if (forceLanguage != null) {
1✔
272
            final LanguageVersion forcedLangVer = configuration.getLanguageVersionDiscoverer()
1✔
273
                    .getDefaultLanguageVersion(forceLanguage);
1✔
274
            configuration.setForceLanguageVersion(forcedLangVer);
1✔
275
        }
276

277
        // Setup CLI message reporter
278
        configuration.setReporter(new SimpleMessageReporter(LoggerFactory.getLogger(PmdCommand.class)));
1✔
279

280
        try {
281
            configuration.prependAuxClasspath(auxClasspath);
1✔
282
        } catch (IllegalArgumentException e) {
×
283
            throw new ParameterException(spec.commandLine(), "Invalid auxiliary classpath: " + e.getMessage(), e);
×
284
        }
1✔
285
        return configuration;
1✔
286
    }
287

288
    @Override
289
    protected @NonNull CliExitCode doExecute(PMDConfiguration configuration) {
290
        if (benchmark) {
1!
291
            TimeTracker.startGlobalTracking();
×
292
        }
293

294
        final PmdReporter pmdReporter = configuration.getReporter();
1✔
295

296
        try {
297
            PmdAnalysis pmd = null;
1✔
298
            final ReportStats stats;
299
            try {
300
                try {
301
                    pmd = PmdAnalysis.create(configuration);
1✔
302
                } catch (final Exception e) {
×
303
                    pmdReporter.errorEx("Could not initialize analysis", e);
×
304
                    return CliExitCode.ERROR;
×
305
                }
1✔
306

307
                LOG.debug("Runtime classpath:\n{}", System.getProperty("java.class.path"));
1✔
308
                LOG.debug("Aux classpath: {}", configuration.getClassLoader());
1✔
309

310
                if (showProgressBar) {
1✔
311
                    if (configuration.getReportFilePath() == null) {
1✔
312
                        pmdReporter.warn("Progressbar rendering conflicts with reporting to STDOUT. "
1✔
313
                                + "No progressbar will be shown. Try running with argument '-r <file>' to output the report to a file instead.");
314
                    } else {
315
                        pmd.addListener(new ProgressBarListener());
1✔
316
                    }
317
                }
318

319
                stats = pmd.runAndReturnStats();
1✔
320
            } finally {
321
                if (pmd != null) {
1!
322
                    pmd.close();
1✔
323
                }
324
            }
325

326
            printSummary(pmdReporter, stats);
1✔
327
            if (pmdReporter.numErrors() > 0) {
1✔
328
                // processing errors are ignored
329
                return CliExitCode.ERROR;
1✔
330
            } else if (stats.getNumErrors() > 0 && configuration.isFailOnError()) {
1✔
331
                return CliExitCode.RECOVERED_ERRORS_OR_VIOLATIONS;
1✔
332
            } else if (stats.getNumViolations() > 0 && configuration.isFailOnViolation()) {
1✔
333
                return CliExitCode.VIOLATIONS_FOUND;
1✔
334
            } else {
335
                return CliExitCode.OK;
1✔
336
            }
337

338
        } catch (final Exception e) {
×
339
            pmdReporter.errorEx("Exception while running PMD.", e);
×
340
            printErrorDetected(pmdReporter, 1);
×
341
            return CliExitCode.ERROR;
×
342
        } finally {
343
            finishBenchmarker(pmdReporter);
1✔
344
        }
345
    }
346

347
    private void printSummary(PmdReporter reporter, ReportStats stats) {
348
        final int violations = stats.getNumViolations();
1✔
349
        final int errors = stats.getNumErrors();
1✔
350
        final StringBuilder message = new StringBuilder();
1✔
351

352
        if (violations == 0) {
1✔
353
            message.append("Found no violations.");
1✔
354
        } else {
355
            message.append("Found ").append(violations)
1✔
356
                   .append(violations == 1 ? " violation." : " violations.");
1✔
357
        }
358

359
        if (errors > 0) {
1✔
360
            message.append(' ')
1✔
361
                   .append(errors == 1
1!
362
                           ? "There was 1 processing error."
1✔
363
                           : "There were " + errors + " processing errors.");
×
364
        }
365

366
        reporter.info(StringUtil.quoteMessageFormat(message.toString()));
1✔
367
    }
1✔
368

369
    private void printErrorDetected(PmdReporter reporter, int errors) {
370
        String msg = LogMessages.errorDetectedMessage(errors, "pmd");
×
371
        // note: using error level here increments the error count of the reporter,
372
        // which we don't want.
373
        reporter.info(StringUtil.quoteMessageFormat(msg));
×
374
    }
×
375

376
    private void finishBenchmarker(final PmdReporter pmdReporter) {
377
        if (benchmark) {
1!
378
            final TimingReport timingReport = TimeTracker.stopGlobalTracking();
×
379

380
            // TODO get specified report format from config
381
            final TimingReportRenderer renderer = new TextTimingReportRenderer();
×
382

383
            try {
384
                // No try-with-resources, do not want to close STDERR
385
                @SuppressWarnings("PMD.CloseResource")
386
                final Writer writer = new OutputStreamWriter(System.err, Charset.defaultCharset());
×
387
                renderer.render(timingReport, writer);
×
388
            } catch (final IOException e) {
×
389
                pmdReporter.errorEx("Error producing benchmark report", e);
×
390
            }
×
391
        }
392
    }
1✔
393

394
    /**
395
     * Provider of candidates for valid report formats.
396
     */
397
    private static final class PmdSupportedReportFormatsCandidates implements Iterable<String> {
398

399
        @Override
400
        public Iterator<String> iterator() {
401
            return RendererFactory.supportedRenderers().iterator();
1✔
402
        }
403
    }
404

405
    /**
406
     * Provider of candidates for valid report properties.
407
     * 
408
     * Check the help for which ones are supported by each report format and possible values.
409
     */
410
    private static final class PmdReportPropertiesCandidates implements Iterable<String> {
411

412
        @Override
413
        public Iterator<String> iterator() {
414
            final List<String> propertyNames = new ArrayList<>();
×
415
            final Properties emptyProps = new Properties();
×
416
            for (final String rendererName : RendererFactory.supportedRenderers()) {
×
417
                final Renderer renderer = RendererFactory.createRenderer(rendererName, emptyProps);
×
418
                
419
                for (final PropertyDescriptor<?> property : renderer.getPropertyDescriptors()) {
×
420
                    propertyNames.add(property.name());
×
421
                }
×
422
            }
×
423
            return propertyNames.iterator();
×
424
        }
425
    }
426
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc