ManualPrepareCommand.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.ArrayList;
8
import java.util.LinkedHashMap;
9
import java.util.List;
10
import java.util.Map;
11
import java.util.logging.Level;
12
import java.util.logging.Logger;
13
import java.util.stream.Collectors;
14
15
import org.egothor.methodatlas.CliConfig;
16
import org.egothor.methodatlas.ManualMode;
17
import org.egothor.methodatlas.ai.AiSuggestionException;
18
import org.egothor.methodatlas.ai.ManualPrepareEngine;
19
import org.egothor.methodatlas.ai.PromptBuilder;
20
import org.egothor.methodatlas.api.DiscoveredMethod;
21
import org.egothor.methodatlas.api.TestDiscovery;
22
import org.egothor.methodatlas.api.TestDiscoveryConfig;
23
24
/**
25
 * CLI command handler for the {@code -manual-prepare} mode.
26
 *
27
 * <p>
28
 * Discovers test classes via the configured {@link TestDiscovery} providers,
29
 * then writes one work file per class to the prepare work directory.
30
 * No CSV output is produced; only progress lines are written to the
31
 * supplied {@link PrintWriter}.
32
 * </p>
33
 *
34
 * @see org.egothor.methodatlas.ai.ManualPrepareEngine
35
 * @see org.egothor.methodatlas.ai.ManualConsumeEngine
36
 */
37
public final class ManualPrepareCommand implements Command {
38
39
    private static final Logger LOG = Logger.getLogger(ManualPrepareCommand.class.getName());
40
41
    private final ManualMode.Prepare prepare;
42
    private final CliConfig cliConfig;
43
    private final TestDiscoveryConfig discoveryConfig;
44
    private final PluginLoader pluginLoader;
45
46
    /**
47
     * Creates a new manual-prepare command.
48
     *
49
     * @param prepare         manual prepare mode configuration (work and response dirs)
50
     * @param cliConfig       full parsed CLI configuration
51
     * @param discoveryConfig discovery configuration forwarded to providers
52
     * @param pluginLoader    loader used to resolve {@link TestDiscovery}
53
     *                        providers from the classpath
54
     */
55
    public ManualPrepareCommand(ManualMode.Prepare prepare, CliConfig cliConfig,
56
            TestDiscoveryConfig discoveryConfig, PluginLoader pluginLoader) {
57
        this.prepare = prepare;
58
        this.cliConfig = cliConfig;
59
        this.discoveryConfig = discoveryConfig;
60
        this.pluginLoader = pluginLoader;
61
    }
62
63
    /**
64
     * Discovers test classes, writes work files, and reports progress.
65
     *
66
     * <p>
67
     * When {@code -redact-comments} is set, every class source is redacted
68
     * <em>before</em> any work file is written; if any class cannot be redacted
69
     * the command aborts with {@link RedactionAbortedException} and writes
70
     * nothing (fail-closed).
71
     * </p>
72
     *
73
     * @param out writer for progress and summary output
74
     * @return {@code 0} if all files were processed successfully, {@code 1} if any
75
     *         provider encountered a processing error
76
     * @throws IOException if traversing a file tree fails
77
     * @throws RedactionAbortedException if redaction is requested and a class
78
     *                                   cannot be redacted
79
     */
80
    @Override
81
    @SuppressWarnings("PMD.CloseResource") // providers closed by closeAll() in the finally block
82
    public int execute(PrintWriter out) throws IOException {
83 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();
84
        List<TestDiscovery> providers = pluginLoader.loadProviders(discoveryConfig);
85
        boolean hadErrors = false;
86
        List<PreparedClass> classes = new ArrayList<>();
87 2 1. execute : removed conditional - replaced equality check with false → KILLED
2. execute : removed conditional - replaced equality check with true → KILLED
        CommentRedaction redaction = cliConfig.aiOptions().redactComments()
88
                ? new CommentRedaction(pluginLoader.loadRedactors(discoveryConfig)) : null;
89
90
        try {
91
            for (Path root : roots) {
92
                List<DiscoveredMethod> allMethods = new ArrayList<>(); // NOPMD – intentionally one list per scan root
93
                for (TestDiscovery provider : providers) {
94
                    provider.discover(root).forEach(allMethods::add);
95 2 1. execute : removed conditional - replaced equality check with false → KILLED
2. execute : removed conditional - replaced equality check with true → KILLED
                    if (provider.hadErrors()) {
96
                        hadErrors = true;
97
                    }
98
                }
99
100
                // Group by FQCN so each class produces one work file.
101
                Map<String, List<DiscoveredMethod>> byClass = allMethods.stream()
102
                        .collect(Collectors.groupingBy(DiscoveredMethod::fqcn,
103
                                LinkedHashMap::new, Collectors.toList()));
104
105
                for (Map.Entry<String, List<DiscoveredMethod>> entry : byClass.entrySet()) {
106
                    List<DiscoveredMethod> classMethods = entry.getValue();
107
                    String classSource = classMethods.get(0).sourceContent().get().orElse(null);
108 2 1. execute : removed conditional - replaced equality check with false → SURVIVED
2. execute : removed conditional - replaced equality check with true → KILLED
                    if (classSource == null) {
109
                        continue;
110
                    }
111 2 1. execute : removed conditional - replaced equality check with true → KILLED
2. execute : removed conditional - replaced equality check with false → KILLED
                    if (redaction != null) {
112
                        classSource = redaction.redactOrThrow(classMethods.get(0).filePath(), classSource);
113
                    }
114
                    classes.add(new PreparedClass(classMethods.get(0).fileStem(), entry.getKey(), classSource,
115
                            classMethods.stream().map(ScanOrchestrator::toTargetMethod).toList()));
116
                }
117
            }
118
        } finally {
119
            pluginLoader.closeAll(providers);
120
        }
121
122
        // Pre-flight complete: every class is redactable (or redaction is off). Only now
123
        // is anything written to the work directory.
124
        ManualPrepareEngine engine;
125
        try {
126
            engine = new ManualPrepareEngine(prepare.workDir(), prepare.responseDir(), cliConfig.aiOptions());
127
        } catch (AiSuggestionException e) {
128
            throw new IllegalStateException("Failed to initialize manual prepare engine", e);
129
        }
130
131
        int prepared = 0;
132
        for (PreparedClass c : classes) {
133
            try {
134
                Path workFile = engine.prepare(c.fileStem(), c.fqcn(), c.source(), c.targets());
135
                out.println("Prepared: " + workFile);
136 1 1. execute : Changed increment from 1 to -1 → SURVIVED
                prepared++;
137
            } catch (AiSuggestionException e) {
138
                if (LOG.isLoggable(Level.WARNING)) {
139
                    LOG.log(Level.WARNING, "Failed to prepare work file for " + c.fqcn(), e);
140
                }
141
            }
142
        }
143
144
        out.println("Manual prepare complete. Wrote " + prepared + " work file(s) to " + prepare.workDir()
145
                + " (response stubs in " + prepare.responseDir() + ")");
146 2 1. execute : removed conditional - replaced equality check with false → KILLED
2. execute : removed conditional - replaced equality check with true → KILLED
        return hadErrors ? 1 : 0;
147
    }
148
149
    /**
150
     * One class ready to be written as a work file.
151
     *
152
     * @param fileStem source file stem
153
     * @param fqcn     fully qualified class name
154
     * @param source   class source to embed in the prompt (redacted when requested)
155
     * @param targets  methods the AI must classify
156
     */
157
    private record PreparedClass(String fileStem, String fqcn, String source,
158
            List<PromptBuilder.TargetMethod> targets) {
159
    }
160
}

Mutations

83

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 → TIMED_OUT

87

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

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

95

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

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

108

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

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

111

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

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

136

1.1
Location : execute
Killed by : none
Changed increment from 1 to -1 → SURVIVED
Covering tests

146

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

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

Active mutators

Tests examined


Report generated by PIT 1.30.1 support