DiffCommand.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.util.List;
7
8
import org.egothor.methodatlas.emit.ClassificationOverride;
9
import org.egothor.methodatlas.emit.DeltaEmitter;
10
import org.egothor.methodatlas.emit.DeltaEntry;
11
import org.egothor.methodatlas.emit.DeltaReport;
12
13
/**
14
 * CLI command handler for the {@code -diff} mode.
15
 *
16
 * <p>
17
 * Compares two MethodAtlas scan CSV outputs and emits a delta report showing
18
 * added, removed, and modified test methods.
19
 * </p>
20
 *
21
 * <p>
22
 * When {@code requireJustification} is {@code true}, every security-relevant
23
 * method that was removed must have a matching entry in the {@code overrideFile}.
24
 * The command exits with code {@code 1} when any unjustified removal is found.
25
 * </p>
26
 *
27
 * @see org.egothor.methodatlas.emit.DeltaReport
28
 * @see org.egothor.methodatlas.emit.DeltaEmitter
29
 */
30
public final class DiffCommand implements Command {
31
32
    private static final int EXIT_GATE_FAILED = 1;
33
34
    private final Path before;
35
    private final Path after;
36
    private final boolean requireJustification;
37
    private final Path overrideFile;
38
39
    /**
40
     * Creates a new diff command without the removal gate.
41
     *
42
     * @param before path to the <em>before</em> scan CSV
43
     * @param after  path to the <em>after</em> scan CSV
44
     */
45
    public DiffCommand(Path before, Path after) {
46
        this(before, after, false, null);
47
    }
48
49
    /**
50
     * Creates a new diff command with optional removal-gate enforcement.
51
     *
52
     * @param before               path to the <em>before</em> scan CSV
53
     * @param after                path to the <em>after</em> scan CSV
54
     * @param requireJustification when {@code true}, exits non-zero if any
55
     *                             security-relevant test was removed without a
56
     *                             matching entry in {@code overrideFile}
57
     * @param overrideFile         path to a MethodAtlas override YAML supplying
58
     *                             removal justifications; ignored when
59
     *                             {@code requireJustification} is {@code false};
60
     *                             {@code null} means no justifications are on file
61
     * @since 3.0.0
62
     */
63
    public DiffCommand(Path before, Path after, boolean requireJustification, Path overrideFile) {
64
        this.before = before;
65
        this.after = after;
66
        this.requireJustification = requireJustification;
67
        this.overrideFile = overrideFile;
68
    }
69
70
    /**
71
     * Computes and emits the delta between the two scan CSV outputs, and
72
     * optionally enforces the removal-justification gate.
73
     *
74
     * @param out writer that receives the delta report
75
     * @return {@code 0} on success; {@code 1} when the removal gate fires
76
     * @throws IOException if either CSV file cannot be read
77
     */
78
    @Override
79
    public int execute(PrintWriter out) throws IOException {
80
        DeltaReport.DeltaResult result = DeltaReport.compute(before, after);
81
        DeltaEmitter.emit(result, out);
82 2 1. execute : removed conditional - replaced equality check with true → KILLED
2. execute : removed conditional - replaced equality check with false → KILLED
        if (requireJustification) {
83 1 1. execute : replaced int return with 0 for org/egothor/methodatlas/command/DiffCommand::execute → KILLED
            return checkRemovals(result, out);
84
        }
85
        return 0;
86
    }
87
88
    /**
89
     * Checks that every security-relevant removal has a justification in the
90
     * override file.
91
     *
92
     * @param result delta result from the comparison
93
     * @param out    writer used to print gate-failure details
94
     * @return {@code 0} when all removals are justified; {@code 1} otherwise
95
     */
96
    private int checkRemovals(DeltaReport.DeltaResult result, PrintWriter out) {
97
        List<DeltaEntry> removals = result.entries().stream()
98 3 1. lambda$checkRemovals$0 : replaced boolean return with true for org/egothor/methodatlas/command/DiffCommand::lambda$checkRemovals$0 → SURVIVED
2. lambda$checkRemovals$0 : removed conditional - replaced equality check with true → SURVIVED
3. lambda$checkRemovals$0 : removed conditional - replaced equality check with false → KILLED
                .filter(e -> e.changeType() == DeltaEntry.ChangeType.REMOVED)
99 2 1. lambda$checkRemovals$1 : replaced boolean return with false for org/egothor/methodatlas/command/DiffCommand::lambda$checkRemovals$1 → KILLED
2. lambda$checkRemovals$1 : replaced boolean return with true for org/egothor/methodatlas/command/DiffCommand::lambda$checkRemovals$1 → KILLED
                .filter(e -> Boolean.TRUE.equals(e.record().aiSecurityRelevant()))
100
                .toList();
101
102 2 1. checkRemovals : removed conditional - replaced equality check with false → SURVIVED
2. checkRemovals : removed conditional - replaced equality check with true → KILLED
        if (removals.isEmpty()) {
103
            return 0;
104
        }
105
106
        ClassificationOverride override = new OverrideLoader().load(overrideFile);
107
        List<DeltaEntry> unjustified = removals.stream()
108 3 1. lambda$checkRemovals$2 : removed conditional - replaced equality check with true → KILLED
2. lambda$checkRemovals$2 : removed conditional - replaced equality check with false → KILLED
3. lambda$checkRemovals$2 : replaced boolean return with true for org/egothor/methodatlas/command/DiffCommand::lambda$checkRemovals$2 → KILLED
                .filter(e -> !override.hasOverrideFor(e.record().fqcn(), e.record().method()))
109
                .toList();
110
111 2 1. checkRemovals : removed conditional - replaced equality check with false → KILLED
2. checkRemovals : removed conditional - replaced equality check with true → KILLED
        if (unjustified.isEmpty()) {
112
            return 0;
113
        }
114
115
        out.println();
116
        out.println("GATE FAILED: " + unjustified.size()
117
                + " security-relevant test(s) removed without justification.");
118
        out.println("Add an entry for each method in an override file and pass it via -override-file:");
119
        for (DeltaEntry e : unjustified) {
120
            out.println("  - " + e.record().fqcn() + " : " + e.record().method());
121
        }
122 1 1. checkRemovals : replaced int return with 0 for org/egothor/methodatlas/command/DiffCommand::checkRemovals → KILLED
        return EXIT_GATE_FAILED;
123
    }
124
}

Mutations

82

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

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

83

1.1
Location : execute
Killed by : org.egothor.methodatlas.command.DiffCommandRemovalGateTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DiffCommandRemovalGateTest]/[method:securityRemoval_noJustification_exits1(java.nio.file.Path)]
replaced int return with 0 for org/egothor/methodatlas/command/DiffCommand::execute → KILLED

98

1.1
Location : lambda$checkRemovals$0
Killed by : org.egothor.methodatlas.command.DiffCommandRemovalGateTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DiffCommandRemovalGateTest]/[method:securityRemoval_noJustification_exits1(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

2.2
Location : lambda$checkRemovals$0
Killed by : none
replaced boolean return with true for org/egothor/methodatlas/command/DiffCommand::lambda$checkRemovals$0 → SURVIVED
Covering tests

3.3
Location : lambda$checkRemovals$0
Killed by : none
removed conditional - replaced equality check with true → SURVIVED Covering tests

99

1.1
Location : lambda$checkRemovals$1
Killed by : org.egothor.methodatlas.command.DiffCommandRemovalGateTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DiffCommandRemovalGateTest]/[method:securityRemoval_noJustification_exits1(java.nio.file.Path)]
replaced boolean return with false for org/egothor/methodatlas/command/DiffCommand::lambda$checkRemovals$1 → KILLED

2.2
Location : lambda$checkRemovals$1
Killed by : org.egothor.methodatlas.command.DiffCommandRemovalGateTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DiffCommandRemovalGateTest]/[method:removedMethodWithoutAiColumn_exits0(java.nio.file.Path)]
replaced boolean return with true for org/egothor/methodatlas/command/DiffCommand::lambda$checkRemovals$1 → KILLED

102

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

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

108

1.1
Location : lambda$checkRemovals$2
Killed by : org.egothor.methodatlas.command.DiffCommandRemovalGateTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DiffCommandRemovalGateTest]/[method:classLevelOverride_justifiesAllRemovals(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

2.2
Location : lambda$checkRemovals$2
Killed by : org.egothor.methodatlas.command.DiffCommandRemovalGateTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DiffCommandRemovalGateTest]/[method:securityRemoval_noJustification_exits1(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

3.3
Location : lambda$checkRemovals$2
Killed by : org.egothor.methodatlas.command.DiffCommandRemovalGateTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DiffCommandRemovalGateTest]/[method:classLevelOverride_justifiesAllRemovals(java.nio.file.Path)]
replaced boolean return with true for org/egothor/methodatlas/command/DiffCommand::lambda$checkRemovals$2 → KILLED

111

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

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

122

1.1
Location : checkRemovals
Killed by : org.egothor.methodatlas.command.DiffCommandRemovalGateTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.DiffCommandRemovalGateTest]/[method:securityRemoval_noJustification_exits1(java.nio.file.Path)]
replaced int return with 0 for org/egothor/methodatlas/command/DiffCommand::checkRemovals → KILLED

Active mutators

Tests examined


Report generated by PIT 1.30.1 support