JsonCommand.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.util.List;
8
import java.util.logging.Level;
9
import java.util.logging.Logger;
10
11
import org.egothor.methodatlas.AiResultCache;
12
import org.egothor.methodatlas.emit.ClassificationOverride;
13
import org.egothor.methodatlas.CliConfig;
14
import org.egothor.methodatlas.emit.TestMethodSink;
15
import org.egothor.methodatlas.ai.AiSuggestionEngine;
16
import org.egothor.methodatlas.api.TestDiscovery;
17
import org.egothor.methodatlas.api.TestDiscoveryConfig;
18
import org.egothor.methodatlas.emit.JsonEmitter;
19
20
/**
21
 * CLI command handler for the {@code -json} output mode.
22
 *
23
 * <p>
24
 * Scans one or more source roots, buffers all discovered test-method records,
25
 * and serializes the result as a flat JSON array once the scan completes.
26
 * </p>
27
 *
28
 * <p>
29
 * The JSON representation differs from CSV in the following ways:
30
 * </p>
31
 * <ul>
32
 * <li>{@code tags} and {@code ai_tags} are JSON arrays, not semicolon-separated
33
 *     strings</li>
34
 * <li>Numeric fields are JSON numbers; {@code ai_security_relevant} is a JSON
35
 *     boolean</li>
36
 * <li>Optional columns are omitted entirely when the corresponding flag is not
37
 *     enabled (rather than being left blank)</li>
38
 * </ul>
39
 *
40
 * @see org.egothor.methodatlas.emit.JsonEmitter
41
 * @see SarifCommand
42
 * @see ScanCommand
43
 */
44
public final class JsonCommand implements Command {
45
46
    private static final Logger LOG = Logger.getLogger(JsonCommand.class.getName());
47
48
    private final CliConfig cliConfig;
49
    private final TestDiscoveryConfig discoveryConfig;
50
    private final AiSuggestionEngine aiEngine;
51
    private final ClassificationOverride override;
52
    private final AiResultCache aiCache;
53
    private final PluginLoader pluginLoader;
54
    private final ScanOrchestrator scanOrchestrator;
55
56
    /**
57
     * Creates a new JSON command.
58
     *
59
     * @param cliConfig         full parsed CLI configuration
60
     * @param discoveryConfig   discovery configuration forwarded to providers
61
     * @param aiEngine          AI engine providing suggestions; {@code null}
62
     *                          when AI is disabled
63
     * @param override          human classification overrides
64
     * @param aiCache           AI result cache
65
     * @param pluginLoader      loader used to resolve and close
66
     *                          {@link TestDiscovery} providers
67
     * @param scanOrchestrator  scan-and-emit orchestrator used to process
68
     *                          each root with the per-record sink
69
     */
70
    public JsonCommand(CliConfig cliConfig, TestDiscoveryConfig discoveryConfig,
71
            AiSuggestionEngine aiEngine, ClassificationOverride override,
72
            AiResultCache aiCache, PluginLoader pluginLoader,
73
            ScanOrchestrator scanOrchestrator) {
74
        this.cliConfig = cliConfig;
75
        this.discoveryConfig = discoveryConfig;
76
        this.aiEngine = aiEngine;
77
        this.override = override;
78
        this.aiCache = aiCache;
79
        this.pluginLoader = pluginLoader;
80
        this.scanOrchestrator = scanOrchestrator;
81
    }
82
83
    /**
84
     * Scans all roots and emits the buffered result as a JSON array.
85
     *
86
     * @param out writer that receives the JSON output
87
     * @return {@code 0} if all files were processed successfully, {@code 1} if
88
     *         any file produced a parse or processing error
89
     * @throws IOException if traversing a file tree fails
90
     */
91
    @Override
92
    public int execute(PrintWriter out) throws IOException {
93 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;
94 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();
95
        boolean contentHashEnabled = cliConfig.contentHash();
96
        boolean emitSourceRoot = cliConfig.emitSourceRoot();
97 2 1. execute : removed conditional - replaced equality check with false → SURVIVED
2. execute : removed conditional - replaced equality check with true → TIMED_OUT
        List<Path> roots = cliConfig.paths().isEmpty() ? List.of(Paths.get(".")) : cliConfig.paths();
98
99
        JsonEmitter jsonEmitter = new JsonEmitter(aiEnabled, confidenceEnabled, contentHashEnabled,
100
                cliConfig.driftDetect(), emitSourceRoot);
101
102
        List<TestDiscovery> providers = pluginLoader.loadProviders(discoveryConfig);
103
        boolean hadErrors = false;
104
        try {
105
            for (Path root : roots) {
106 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;
107
                final String finalSourceRoot = sourceRoot;
108
                TestMethodSink rootSink = (fqcn, method, beginLine, loc, contentHash, tags, displayName, suggestion) ->
109 1 1. lambda$execute$0 : removed call to org/egothor/methodatlas/emit/JsonEmitter::record → KILLED
                        jsonEmitter.record(fqcn, method, beginLine, loc, contentHash, tags, displayName,
110
                                suggestion, finalSourceRoot);
111 2 1. execute : removed conditional - replaced equality check with false → SURVIVED
2. execute : removed conditional - replaced equality check with true → SURVIVED
                if (scanOrchestrator.runDiscovery(root, providers, cliConfig.aiOptions(), aiEngine,
112
                        scanOrchestrator.filterSink(rootSink, cliConfig.securityOnly(),
113
                                cliConfig.minConfidence(), confidenceEnabled),
114
                        cliConfig.contentHash(), override, aiCache)) {
115
                    hadErrors = true;
116
                }
117
            }
118
        } finally {
119 1 1. execute : removed call to org/egothor/methodatlas/command/PluginLoader::closeAll → SURVIVED
            pluginLoader.closeAll(providers);
120
        }
121
122
        if (aiCache.isActive() && LOG.isLoggable(Level.INFO)) {
123
            LOG.log(Level.INFO, "AI cache: {0} hit(s), {1} miss(es)",
124
                    new Object[] { aiCache.hits(), aiCache.misses() });
125
        }
126
127 1 1. execute : removed call to org/egothor/methodatlas/emit/JsonEmitter::flush → KILLED
        jsonEmitter.flush(out);
128 2 1. execute : removed conditional - replaced equality check with true → SURVIVED
2. execute : removed conditional - replaced equality check with false → SURVIVED
        return hadErrors ? 1 : 0;
129
    }
130
}

Mutations

93

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

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

94

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.MethodAtlasAppJsonTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppJsonTest]/[method:jsonMode_withAi_aiTagsIsArray(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:jsonMode_withAiAndConfidence_confidenceIsNumber(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:jsonMode_withAiAndConfidence_confidenceIsNumber(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

97

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

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

106

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

109

1.1
Location : lambda$execute$0
Killed by : org.egothor.methodatlas.MethodAtlasAppJsonTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppJsonTest]/[method:jsonMode_producesValidArray(java.nio.file.Path)]
removed call to org/egothor/methodatlas/emit/JsonEmitter::record → 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

119

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

127

1.1
Location : execute
Killed by : org.egothor.methodatlas.MethodAtlasAppJsonTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppJsonTest]/[method:jsonMode_producesValidArray(java.nio.file.Path)]
removed call to org/egothor/methodatlas/emit/JsonEmitter::flush → KILLED

128

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

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

Active mutators

Tests examined


Report generated by PIT 1.22.1