ScanCommand.java

1
package org.egothor.methodatlas.command;
2
3
import java.io.IOException;
4
import java.io.PrintWriter;
5
import java.nio.file.Path;
6
import java.nio.file.Paths;
7
import java.time.Instant;
8
import java.util.List;
9
import java.util.logging.Level;
10
import java.util.logging.Logger;
11
12
import org.egothor.methodatlas.AiResultCache;
13
import org.egothor.methodatlas.emit.ClassificationOverride;
14
import org.egothor.methodatlas.CliConfig;
15
import org.egothor.methodatlas.emit.OutputMode;
16
import org.egothor.methodatlas.emit.TestMethodSink;
17
import org.egothor.methodatlas.ai.AiSuggestionEngine;
18
import org.egothor.methodatlas.api.TestDiscovery;
19
import org.egothor.methodatlas.api.TestDiscoveryConfig;
20
import org.egothor.methodatlas.emit.OutputEmitter;
21
22
/**
23
 * CLI command handler for the default CSV and {@code -plain} output modes.
24
 *
25
 * <p>
26
 * Scans one or more source roots, optionally enriches the output with AI
27
 * suggestions, and emits test-method records incrementally to the supplied
28
 * writer.
29
 * </p>
30
 *
31
 * @see org.egothor.methodatlas.emit.OutputEmitter
32
 * @see SarifCommand
33
 * @see GitHubAnnotationsCommand
34
 */
35
public final class ScanCommand implements Command {
36
37
    private static final Logger LOG = Logger.getLogger(ScanCommand.class.getName());
38
39
    private final CliConfig cliConfig;
40
    private final TestDiscoveryConfig discoveryConfig;
41
    private final AiSuggestionEngine aiEngine;
42
    private final ClassificationOverride override;
43
    private final AiResultCache aiCache;
44
    private final PluginLoader pluginLoader;
45
    private final ScanOrchestrator scanOrchestrator;
46
47
    /**
48
     * Creates a new scan command.
49
     *
50
     * @param cliConfig         full parsed CLI configuration
51
     * @param discoveryConfig   discovery configuration forwarded to providers
52
     * @param aiEngine          AI engine providing suggestions; {@code null}
53
     *                          when AI is disabled
54
     * @param override          human classification overrides
55
     * @param aiCache           AI result cache
56
     * @param pluginLoader      loader used to resolve and close
57
     *                          {@link TestDiscovery} providers
58
     * @param scanOrchestrator  scan-and-emit orchestrator used to process
59
     *                          each root with the configured per-record sink
60
     */
61
    public ScanCommand(CliConfig cliConfig, TestDiscoveryConfig discoveryConfig,
62
            AiSuggestionEngine aiEngine, ClassificationOverride override,
63
            AiResultCache aiCache, PluginLoader pluginLoader,
64
            ScanOrchestrator scanOrchestrator) {
65
        this.cliConfig = cliConfig;
66
        this.discoveryConfig = discoveryConfig;
67
        this.aiEngine = aiEngine;
68
        this.override = override;
69
        this.aiCache = aiCache;
70
        this.pluginLoader = pluginLoader;
71
        this.scanOrchestrator = scanOrchestrator;
72
    }
73
74
    /**
75
     * Runs the scan and emits output incrementally.
76
     *
77
     * @param out writer that receives all emitted output
78
     * @return {@code 0} if all files were processed successfully, {@code 1} if
79
     *         any file produced a parse or processing error
80
     * @throws IOException if traversing a file tree fails
81
     */
82
    @Override
83
    @SuppressWarnings("PMD.NPathComplexity") // combinatorial expansion of optional CSV columns; inherent to the format
84
    public int execute(PrintWriter out) throws IOException {
85 2 1. execute : removed conditional - replaced equality check with false → KILLED
2. execute : removed conditional - replaced equality check with true → KILLED
        boolean aiEnabled = aiEngine != null;
86 4 1. execute : removed conditional - replaced equality check with true → SURVIVED
2. execute : removed conditional - replaced equality check with true → KILLED
3. execute : removed conditional - replaced equality check with false → KILLED
4. execute : removed conditional - replaced equality check with false → KILLED
        boolean confidenceEnabled = aiEnabled && cliConfig.aiOptions().confidence();
87
        boolean contentHashEnabled = cliConfig.contentHash();
88 2 1. execute : removed conditional - replaced equality check with false → SURVIVED
2. execute : removed conditional - replaced equality check with true → KILLED
        List<Path> roots = cliConfig.paths().isEmpty() ? List.of(Paths.get(".")) : cliConfig.paths();
89
90
        OutputEmitter emitter = new OutputEmitter(out, aiEnabled, confidenceEnabled, contentHashEnabled,
91
                cliConfig.driftDetect(), cliConfig.emitSourceRoot());
92
93 2 1. execute : removed conditional - replaced equality check with true → KILLED
2. execute : removed conditional - replaced equality check with false → KILLED
        if (cliConfig.emitMetadata()) {
94
            String version = ScanCommand.class.getPackage().getImplementationVersion();
95
            String taxonomyInfo = new AiRuntimeBuilder().resolveTaxonomyInfo(cliConfig.aiOptions(), aiEnabled);
96 3 1. execute : removed conditional - replaced equality check with false → SURVIVED
2. execute : removed conditional - replaced equality check with true → SURVIVED
3. execute : removed call to org/egothor/methodatlas/emit/OutputEmitter::emitMetadata → KILLED
            emitter.emitMetadata(version != null ? version : "dev", Instant.now().toString(), taxonomyInfo);
97
        }
98
99 1 1. execute : removed call to org/egothor/methodatlas/emit/OutputEmitter::emitCsvHeader → KILLED
        emitter.emitCsvHeader(cliConfig.outputMode());
100
101
        final OutputMode mode = cliConfig.outputMode();
102
        final boolean emitSourceRoot = cliConfig.emitSourceRoot();
103
104
        // Scan each root with its own sink so the source_root value can be captured
105
        // per root. When emitSourceRoot is false, sourceRoot is null and the column
106
        // is omitted from the output.
107
        List<TestDiscovery> providers = pluginLoader.loadProviders(discoveryConfig);
108
        boolean hadErrors = false;
109
        try {
110
            for (Path root : roots) {
111 2 1. execute : removed conditional - replaced equality check with false → SURVIVED
2. execute : removed conditional - replaced equality check with true → SURVIVED
                String sourceRoot = emitSourceRoot ? ContentHasher.filePrefix(List.of(root)) : null;
112
                TestMethodSink rootSink = (fqcn, method, beginLine, loc, contentHash, tags, displayName, suggestion) ->
113 1 1. lambda$execute$0 : removed call to org/egothor/methodatlas/emit/OutputEmitter::emit → KILLED
                        emitter.emit(mode, fqcn, method, loc, contentHash, tags, displayName, suggestion, sourceRoot);
114 2 1. execute : removed conditional - replaced equality check with true → SURVIVED
2. execute : removed conditional - replaced equality check with false → KILLED
                if (scanOrchestrator.runDiscovery(root, providers, cliConfig.aiOptions(), aiEngine,
115
                        scanOrchestrator.filterSink(rootSink, cliConfig.securityOnly(),
116
                                cliConfig.minConfidence(), confidenceEnabled),
117
                        cliConfig.contentHash(), override, aiCache)) {
118
                    hadErrors = true;
119
                }
120
            }
121
        } finally {
122 1 1. execute : removed call to org/egothor/methodatlas/command/PluginLoader::closeAll → SURVIVED
            pluginLoader.closeAll(providers);
123
        }
124
125
        if (aiCache.isActive() && LOG.isLoggable(Level.INFO)) {
126
            LOG.log(Level.INFO, "AI cache: {0} hit(s), {1} miss(es)",
127
                    new Object[] { aiCache.hits(), aiCache.misses() });
128
        }
129 2 1. execute : removed conditional - replaced equality check with true → SURVIVED
2. execute : removed conditional - replaced equality check with false → KILLED
        return hadErrors ? 1 : 0;
130
    }
131
}

Mutations

85

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:emitMetadata_aiEnabled_emitsBuiltinDefaultTaxonomy(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

2.2
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppContentHashTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppContentHashTest]/[method:csvMode_headerIncludesContentHashColumn_whenEnabled(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

86

1.1
Location : execute
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

2.2
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppManualTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppManualTest]/[method:manualConsume_emitsEmptyAiColumnsForClassesWithoutResponseFile(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

3.3
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppJsonTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppJsonTest]/[method:minConfidence_filtersLowConfidenceMethods(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

4.4
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppJsonTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppJsonTest]/[method:minConfidence_filtersLowConfidenceMethods(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

88

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:csvMode_unparseableFile_returnsExitCode1(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

2.2
Location : execute
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

93

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:csvMode_noPackageDeclaration_usesSimpleClassNameAsFqcn(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

2.2
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:emitMetadata_aiEnabled_emitsBuiltinDefaultTaxonomy(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

96

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:emitMetadata_aiEnabled_emitsBuiltinDefaultTaxonomy(java.nio.file.Path)]
removed call to org/egothor/methodatlas/emit/OutputEmitter::emitMetadata → KILLED

2.2
Location : execute
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

3.3
Location : execute
Killed by : none
removed conditional - replaced equality check with true → SURVIVED Covering tests

99

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:csvMode_noPackageDeclaration_usesSimpleClassNameAsFqcn(java.nio.file.Path)]
removed call to org/egothor/methodatlas/emit/OutputEmitter::emitCsvHeader → KILLED

111

1.1
Location : execute
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

2.2
Location : execute
Killed by : none
removed conditional - replaced equality check with true → SURVIVED Covering tests

113

1.1
Location : lambda$execute$0
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:csvMode_noPackageDeclaration_usesSimpleClassNameAsFqcn(java.nio.file.Path)]
removed call to org/egothor/methodatlas/emit/OutputEmitter::emit → KILLED

114

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:csvMode_unparseableFile_returnsExitCode1(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

2.2
Location : execute
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

122

1.1
Location : execute
Killed by : none
removed call to org/egothor/methodatlas/command/PluginLoader::closeAll → SURVIVED
Covering tests

129

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:csvMode_unparseableFile_returnsExitCode1(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

2.2
Location : execute
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

Active mutators

Tests examined


Report generated by PIT 1.22.1