ControllerConfigLoader.java

1
package com.rox.input;
2
3
import de.gurkenlabs.input4j.ComponentType;
4
import de.gurkenlabs.input4j.InputComponent;
5
import de.gurkenlabs.input4j.InputDevice;
6
import de.gurkenlabs.input4j.InputDevicePlugin;
7
import de.gurkenlabs.input4j.InputDevices;
8
9
import java.awt.event.KeyEvent;
10
import java.io.IOException;
11
import java.io.InputStream;
12
import java.lang.reflect.Field;
13
import java.nio.file.Files;
14
import java.nio.file.Path;
15
import java.util.Collection;
16
import java.util.HashMap;
17
import java.util.List;
18
import java.util.Locale;
19
import java.util.Map;
20
import java.util.Optional;
21
import java.util.Properties;
22
import java.util.function.Supplier;
23
import java.util.logging.Logger;
24
import java.util.stream.Collectors;
25
26
/**
27
 * Loads a {@link ControllerConfiguration} from a {@code .properties} file - see
28
 * {@code controllers.properties.example} at the repo root for the format. {@link #load} is the thin
29
 * I/O entry point; {@link #parse(Properties)} is the pure, directly-testable core that does the actual
30
 * interpretation.
31
 */
32
public final class ControllerConfigLoader {
33
    private static final String FOUR_SCORE_ENABLED_KEY = "fourscore.enabled";
34
    private static final int PLAYER_COUNT = 4;
35
    private static final float DEFAULT_AXIS_THRESHOLD = 0.5f;
36
    private static final Logger log = Logger.getLogger(ControllerConfigLoader.class.getName());
37
38
    //input4j's own InputComponent.ID.get(String) only finds IDs whose owning component class has
39
    //already been loaded by the JVM (each class registers its constants as a static-initializer side
40
    //effect) - relying on that here would make a config's resolvability depend on incidental
41
    //class-loading order (e.g. which real devices happened to be enumerated first), not the config
42
    //itself. Reading de.gurkenlabs.input4j.components.Button/Axis's own declared fields directly (the
43
    //same reflection approach as resolveKeyCode below, against KeyEvent.VK_*) sidesteps that entirely -
44
    //friendly per-controller-type names (e.g. XInput's "A") are deliberately not included here since
45
    //ID's own equals()/registration logic means only the canonical generic name ever survives anyway.
46
    private static final Map<String, InputComponent.ID> KNOWN_COMPONENTS = loadKnownComponents();
47
48
    private ControllerConfigLoader(){
49
    }
50
51
    private static Map<String, InputComponent.ID> loadKnownComponents(){
52
        final Map<String, InputComponent.ID> components = new HashMap<>();
53
        for (final Class<?> componentClass : List.of(
54
                de.gurkenlabs.input4j.components.Button.class, de.gurkenlabs.input4j.components.Axis.class)){
55
            for (final Field field : componentClass.getFields()){
56
                if (InputComponent.ID.class.isAssignableFrom(field.getType())){
57
                    try {
58
                        final InputComponent.ID id = (InputComponent.ID) field.get(null);
59
                        components.put(id.name, id);
60
                    } catch (IllegalAccessException e){
61
                        throw new IllegalStateException("Could not read input4j component field " + field, e);
62
                    }
63
                }
64
            }
65
        }
66
        return components;
67
    }
68
69
    public static ControllerConfiguration load(final Path path) throws IOException {
70
        final Properties properties = new Properties();
71
        try (InputStream in = Files.newInputStream(path)){
72 1 1. load : removed call to java/util/Properties::load → KILLED
            properties.load(in);
73
        }
74 1 1. load : replaced return value with null for com/rox/input/ControllerConfigLoader::load → KILLED
        return parse(properties);
75
    }
76
77
    public static ControllerConfiguration parse(final Properties properties){
78 1 1. parse : replaced return value with null for com/rox/input/ControllerConfigLoader::parse → KILLED
        return parse(properties, ControllerConfigLoader::availableDevices);
79
    }
80
81
    /**
82
     * Test seam: a fake {@code deviceSupplier} makes {@code source=gamepad} parsing fully testable
83
     * without a real physical gamepad, mirroring how {@code SpeakerAudioOutput} takes a real-line
84
     * seam - matches {@link #availableDevices()}'s own doc for why that's the one genuinely
85
     * hardware-dependent piece of this class.
86
     */
87
    static ControllerConfiguration parse(final Properties properties, final Supplier<Collection<InputDevice>> deviceSupplier){
88
        final boolean fourScoreEnabled = Boolean.parseBoolean(properties.getProperty(FOUR_SCORE_ENABLED_KEY, "false"));
89
        final LazyDevices devices = new LazyDevices(deviceSupplier);
90
        final Controller[] players = new Controller[PLAYER_COUNT];
91 2 1. parse : negated conditional → KILLED
2. parse : changed conditional boundary → KILLED
        for (int i = 0; i < PLAYER_COUNT; i++){
92 1 1. parse : Replaced integer addition with subtraction → KILLED
            players[i] = parsePlayer(properties, i + 1, devices);
93
        }
94 1 1. parse : replaced return value with null for com/rox/input/ControllerConfigLoader::parse → KILLED
        return new ControllerConfiguration(players[0], players[1], players[2], players[3], fourScoreEnabled);
95
    }
96
97
    //the plugin returned by the one real InputDevices.init() call this class ever makes (from
98
    //availableDevices() below) - input4j documents that result as owning real hardware resources
99
    //(open device handles), not something whose device list can just be read and discarded. There is
100
    //no config-reload path in this codebase, so tracking only the single most-recently-opened plugin
101
    //(rather than e.g. a per-ControllerConfiguration handle) is a deliberate, sufficient simplification.
102
    private static volatile InputDevicePlugin activePlugin;
103
104
    /**
105
     * Enumerates real, currently-connected gamepads via input4j.
106
     * <p>
107
     * XXX Mutation coverage expected to have issues here since it deals with real hardware (and CI has
108
     * no gamepad attached) - just accepting it for now, same as {@code SpeakerAudioOutput.openDefaultLine()}.
109
     */
110
    private static Collection<InputDevice> availableDevices(){
111
        final InputDevicePlugin plugin = InputDevices.init();
112
        activePlugin = plugin;
113 2 1. availableDevices : replaced return value with Collections.emptyList for com/rox/input/ControllerConfigLoader::availableDevices → NO_COVERAGE
2. availableDevices : negated conditional → NO_COVERAGE
        return plugin == null ? List.of() : plugin.getAll();
114
    }
115
116
    /**
117
     * Closes the input4j plugin opened by the most recent real gamepad enumeration (a no-op if
118
     * gamepads were never used, or none are currently open) - callers should call this once emulation
119
     * has stopped polling gamepads, mirroring how {@code RomVideoSmokeDemo} already closes its
120
     * {@code SwingVideoOutput} in its own outer {@code finally}.
121
     * <p>
122
     * XXX Mutation coverage expected to have issues here since it deals with real hardware (and CI has
123
     * no gamepad attached) - just accepting it for now, same as {@link #availableDevices()}.
124
     */
125
    public static void closeConnectedGamepads() throws IOException {
126
        final InputDevicePlugin plugin = activePlugin;
127 1 1. closeConnectedGamepads : negated conditional → KILLED
        if (plugin != null){
128
            activePlugin = null;
129 1 1. closeConnectedGamepads : removed call to de/gurkenlabs/input4j/InputDevicePlugin::close → KILLED
            plugin.close();
130
        }
131
    }
132
133
    /** Test seam: lets a test drive {@link #closeConnectedGamepads()}'s "an open plugin gets closed" path with a fake plugin, without ever touching real hardware. */
134
    static void setActivePluginForTesting(final InputDevicePlugin plugin){
135
        activePlugin = plugin;
136
    }
137
138
    /** Only calls {@code deviceSupplier} if some player actually needs a gamepad - not for keyboard/none-only configs. */
139
    private static final class LazyDevices {
140
        private final Supplier<Collection<InputDevice>> source;
141
        private Collection<InputDevice> cached;
142
143
        LazyDevices(final Supplier<Collection<InputDevice>> source){
144
            this.source = source;
145
        }
146
147
        Collection<InputDevice> get(){
148 1 1. get : negated conditional → KILLED
            if (cached == null){
149
                cached = source.get();
150
            }
151 1 1. get : replaced return value with Collections.emptyList for com/rox/input/ControllerConfigLoader$LazyDevices::get → KILLED
            return cached;
152
        }
153
    }
154
155
    private static Controller parsePlayer(final Properties properties, final int playerNumber, final LazyDevices devices){
156
        final String prefix = "player" + playerNumber;
157
        final String source = properties.getProperty(prefix + ".source", "none");
158 1 1. parsePlayer : negated conditional → KILLED
        if ("keyboard".equalsIgnoreCase(source)){
159 1 1. parsePlayer : replaced return value with null for com/rox/input/ControllerConfigLoader::parsePlayer → KILLED
            return new KeyboardController(parseKeyBindings(properties, prefix));
160
        }
161 1 1. parsePlayer : negated conditional → KILLED
        if ("gamepad".equalsIgnoreCase(source)){
162
            //parsed even when the gamepad isn't connected, so a typo isn't hidden until it's plugged in
163
            final Map<Button, GamepadBinding> bindings = parseGamepadBindings(properties, prefix);
164 1 1. parsePlayer : replaced return value with null for com/rox/input/ControllerConfigLoader::parsePlayer → KILLED
            return findDevice(properties, prefix, devices.get())
165 1 1. lambda$parsePlayer$0 : replaced return value with null for com/rox/input/ControllerConfigLoader::lambda$parsePlayer$0 → KILLED
                    .<Controller>map(device -> new GamepadController(device, bindings))
166
                    .orElse(Controller.NONE);
167
        }
168 1 1. parsePlayer : negated conditional → KILLED
        if ("none".equalsIgnoreCase(source)){
169 1 1. parsePlayer : replaced return value with null for com/rox/input/ControllerConfigLoader::parsePlayer → KILLED
            return Controller.NONE;
170
        }
171
        throw new IllegalArgumentException("Unknown controller source '" + source + "' for " + prefix);
172
    }
173
174
    /**
175
     * The configured gamepad, or empty (with a warning) if it isn't connected - that player then starts
176
     * with no controller rather than the emulator failing to start.
177
     *
178
     * @throws IllegalArgumentException for a negative {@code deviceIndex} - a config mistake, not a missing pad
179
     */
180
    private static Optional<InputDevice> findDevice(final Properties properties, final String prefix, final Collection<InputDevice> devices){
181
        final List<InputDevice> deviceList = List.copyOf(devices);
182
        final String nameSubstring = properties.getProperty(prefix + ".gamepad.device");
183 1 1. findDevice : negated conditional → KILLED
        if (nameSubstring != null){
184
            final String needle = nameSubstring.toLowerCase(Locale.ROOT);
185
            final Optional<InputDevice> match = deviceList.stream()
186 2 1. lambda$findDevice$0 : replaced boolean return with false for com/rox/input/ControllerConfigLoader::lambda$findDevice$0 → KILLED
2. lambda$findDevice$0 : replaced boolean return with true for com/rox/input/ControllerConfigLoader::lambda$findDevice$0 → KILLED
                    .filter(d -> d.getDisplayName().toLowerCase(Locale.ROOT).contains(needle))
187
                    .findFirst();
188 1 1. findDevice : negated conditional → KILLED
            if (match.isEmpty()){
189 1 1. findDevice : removed call to com/rox/input/ControllerConfigLoader::warnMissing → KILLED
                warnMissing("No connected gamepad matches '" + nameSubstring + "'", prefix, deviceList);
190
            }
191 1 1. findDevice : replaced return value with Optional.empty for com/rox/input/ControllerConfigLoader::findDevice → KILLED
            return match;
192
        }
193
194
        //no name given - fall back to enumeration order, which is stabler within a single run than
195
        //across runs/OS-reboots, hence name-substring being the recommended, primary option above
196
        final int index = Integer.parseInt(properties.getProperty(prefix + ".gamepad.deviceIndex", "0"));
197 2 1. findDevice : negated conditional → KILLED
2. findDevice : changed conditional boundary → KILLED
        if (index < 0){
198
            throw new IllegalArgumentException("Gamepad deviceIndex " + index + " for " + prefix + " can't be negative");
199
        }
200 2 1. findDevice : changed conditional boundary → KILLED
2. findDevice : negated conditional → KILLED
        if (index >= deviceList.size()){
201 1 1. findDevice : removed call to com/rox/input/ControllerConfigLoader::warnMissing → KILLED
            warnMissing("No connected gamepad at index " + index, prefix, deviceList);
202
            return Optional.empty();
203
        }
204 1 1. findDevice : replaced return value with Optional.empty for com/rox/input/ControllerConfigLoader::findDevice → KILLED
        return Optional.of(deviceList.get(index));
205
    }
206
207
    private static void warnMissing(final String problem, final String prefix, final List<InputDevice> connected){
208 1 1. warnMissing : negated conditional → KILLED
        final String found = connected.isEmpty()
209
                ? "none"
210 1 1. lambda$warnMissing$0 : replaced return value with "" for com/rox/input/ControllerConfigLoader::lambda$warnMissing$0 → KILLED
                : connected.stream().map(d -> "'" + d.getDisplayName() + "'").collect(Collectors.joining(", "));
211
        log.warning(problem + " for " + prefix + " (connected: " + found + ") - starting " + prefix + " with no controller");
212
    }
213
214
    private static Map<Button, GamepadBinding> parseGamepadBindings(final Properties properties, final String prefix){
215
        final Map<Button, GamepadBinding> bindings = new HashMap<>();
216
        for (final Button button : Button.values()){
217
            final String value = properties.getProperty(prefix + ".gamepad." + button.name().toLowerCase(Locale.ROOT));
218 1 1. parseGamepadBindings : negated conditional → KILLED
            if (value != null){
219
                bindings.put(button, resolveGamepadBinding(value, prefix, button));
220
            }
221
        }
222 1 1. parseGamepadBindings : replaced return value with Collections.emptyMap for com/rox/input/ControllerConfigLoader::parseGamepadBindings → KILLED
        return bindings;
223
    }
224
225
    private static GamepadBinding resolveGamepadBinding(final String value, final String prefix, final Button button){
226
        final String[] parts = value.split(":", 2);
227
        final String componentName = parts[0].trim();
228
        final InputComponent.ID componentId = KNOWN_COMPONENTS.get(componentName);
229 1 1. resolveGamepadBinding : negated conditional → KILLED
        if (componentId == null){
230
            throw new IllegalArgumentException(
231
                    "Unknown gamepad component '" + componentName + "' bound to " + button + " for " + prefix);
232
        }
233 1 1. resolveGamepadBinding : negated conditional → KILLED
        if (componentId.type != ComponentType.AXIS){
234 2 1. resolveGamepadBinding : changed conditional boundary → KILLED
2. resolveGamepadBinding : negated conditional → KILLED
            if (parts.length > 1){
235
                throw new IllegalArgumentException(
236
                        "Button component '" + componentName + "' bound to " + button + " for " + prefix
237
                                + " must not have a direction suffix");
238
            }
239 1 1. resolveGamepadBinding : replaced return value with null for com/rox/input/ControllerConfigLoader::resolveGamepadBinding → KILLED
            return new GamepadBinding.ButtonBinding(componentId);
240
        }
241 2 1. resolveGamepadBinding : negated conditional → KILLED
2. resolveGamepadBinding : changed conditional boundary → KILLED
        if (parts.length < 2){
242
            throw new IllegalArgumentException(
243
                    "Axis component '" + componentName + "' bound to " + button + " for " + prefix
244
                            + " needs a direction suffix, e.g. '" + componentName + ":positive'");
245
        }
246
        final boolean positive = switch (parts[1].trim().toLowerCase(Locale.ROOT)) {
247
            case "positive" -> true;
248
            case "negative" -> false;
249
            default -> throw new IllegalArgumentException(
250
                    "Unknown axis direction '" + parts[1] + "' bound to " + button + " for " + prefix
251
                            + " - expected 'positive' or 'negative'");
252
        };
253 1 1. resolveGamepadBinding : replaced return value with null for com/rox/input/ControllerConfigLoader::resolveGamepadBinding → KILLED
        return new GamepadBinding.AxisBinding(componentId, positive, DEFAULT_AXIS_THRESHOLD);
254
    }
255
256
    private static Map<Integer, Button> parseKeyBindings(final Properties properties, final String prefix){
257
        final Map<Integer, Button> bindings = new HashMap<>();
258
        for (final Button button : Button.values()){
259
            final String keyName = properties.getProperty(prefix + ".keyboard." + button.name().toLowerCase());
260 1 1. parseKeyBindings : negated conditional → KILLED
            if (keyName != null){
261
                bindings.put(resolveKeyCode(keyName), button);
262
            }
263
        }
264 1 1. parseKeyBindings : replaced return value with Collections.emptyMap for com/rox/input/ControllerConfigLoader::parseKeyBindings → KILLED
        return bindings;
265
    }
266
267
    private static int resolveKeyCode(final String keyName){
268
        try {
269
            //Locale.ROOT, not the JVM default - a Turkish default locale uppercases "i" to "İ" (dotted
270
            //capital I), which would look up a nonexistent "VK_İ..." field instead of "VK_I..."
271 1 1. resolveKeyCode : replaced int return with 0 for com/rox/input/ControllerConfigLoader::resolveKeyCode → KILLED
            return KeyEvent.class.getField("VK_" + keyName.toUpperCase(Locale.ROOT)).getInt(null);
272
        } catch (NoSuchFieldException | IllegalAccessException e){
273
            throw new IllegalArgumentException(
274
                    "Unknown key name '" + keyName + "' - expected a java.awt.event.KeyEvent.VK_* field name", e);
275
        }
276
    }
277
}

Mutations

72

1.1
Location : load
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:loadReadsAPropertiesFileFromDisk(java.nio.file.Path)]
removed call to java/util/Properties::load → KILLED

74

1.1
Location : load
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:loadReadsAPropertiesFileFromDisk(java.nio.file.Path)]
replaced return value with null for com/rox/input/ControllerConfigLoader::load → KILLED

78

1.1
Location : parse
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:noneSourceIsControllerNone()]
replaced return value with null for com/rox/input/ControllerConfigLoader::parse → KILLED

91

1.1
Location : parse
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:unknownSourceThrows()]
negated conditional → KILLED

2.2
Location : parse
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:noneSourceIsControllerNone()]
changed conditional boundary → KILLED

92

1.1
Location : parse
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:loadReadsAPropertiesFileFromDisk(java.nio.file.Path)]
Replaced integer addition with subtraction → KILLED

94

1.1
Location : parse
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:fourScoreEnabledParsesTrue()]
replaced return value with null for com/rox/input/ControllerConfigLoader::parse → KILLED

113

1.1
Location : availableDevices
Killed by : none
replaced return value with Collections.emptyList for com/rox/input/ControllerConfigLoader::availableDevices → NO_COVERAGE

2.2
Location : availableDevices
Killed by : none
negated conditional → NO_COVERAGE

127

1.1
Location : closeConnectedGamepads
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:closeConnectedGamepadsIsANoOpWhenNoGamepadWasEverOpened()]
negated conditional → KILLED

129

1.1
Location : closeConnectedGamepads
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:closeConnectedGamepadsClosesAnOpenPluginExactlyOnce()]
removed call to de/gurkenlabs/input4j/InputDevicePlugin::close → KILLED

148

1.1
Location : get
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceWithANegativeDeviceIndexThrows()]
negated conditional → KILLED

151

1.1
Location : get
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceSelectsDeviceByExplicitIndex()]
replaced return value with Collections.emptyList for com/rox/input/ControllerConfigLoader$LazyDevices::get → KILLED

158

1.1
Location : parsePlayer
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:unknownSourceThrows()]
negated conditional → KILLED

159

1.1
Location : parsePlayer
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:loadReadsAPropertiesFileFromDisk(java.nio.file.Path)]
replaced return value with null for com/rox/input/ControllerConfigLoader::parsePlayer → KILLED

161

1.1
Location : parsePlayer
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:unknownSourceThrows()]
negated conditional → KILLED

164

1.1
Location : parsePlayer
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:noGamepadsConnectedAtAllSaysSo()]
replaced return value with null for com/rox/input/ControllerConfigLoader::parsePlayer → KILLED

165

1.1
Location : lambda$parsePlayer$0
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceSelectsDeviceByExplicitIndex()]
replaced return value with null for com/rox/input/ControllerConfigLoader::lambda$parsePlayer$0 → KILLED

168

1.1
Location : parsePlayer
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:fourScoreEnabledParsesTrue()]
negated conditional → KILLED

169

1.1
Location : parsePlayer
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:noneSourceIsControllerNone()]
replaced return value with null for com/rox/input/ControllerConfigLoader::parsePlayer → KILLED

183

1.1
Location : findDevice
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceWithANegativeDeviceIndexThrows()]
negated conditional → KILLED

186

1.1
Location : lambda$findDevice$0
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceSelectsDeviceByNameSubstringCaseInsensitively()]
replaced boolean return with false for com/rox/input/ControllerConfigLoader::lambda$findDevice$0 → KILLED

2.2
Location : lambda$findDevice$0
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceSelectsDeviceByNameSubstringCaseInsensitively()]
replaced boolean return with true for com/rox/input/ControllerConfigLoader::lambda$findDevice$0 → KILLED

188

1.1
Location : findDevice
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceWithNoMatchingDeviceNameWarnsAndStartsThatPlayerWithNoController()]
negated conditional → KILLED

189

1.1
Location : findDevice
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceWithNoMatchingDeviceNameWarnsAndStartsThatPlayerWithNoController()]
removed call to com/rox/input/ControllerConfigLoader::warnMissing → KILLED

191

1.1
Location : findDevice
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceSelectsDeviceByNameSubstringCaseInsensitively()]
replaced return value with Optional.empty for com/rox/input/ControllerConfigLoader::findDevice → KILLED

197

1.1
Location : findDevice
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceWithANegativeDeviceIndexThrows()]
negated conditional → KILLED

2.2
Location : findDevice
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:noGamepadsConnectedAtAllSaysSo()]
changed conditional boundary → KILLED

200

1.1
Location : findDevice
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:noGamepadsConnectedAtAllSaysSo()]
changed conditional boundary → KILLED

2.2
Location : findDevice
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:noGamepadsConnectedAtAllSaysSo()]
negated conditional → KILLED

201

1.1
Location : findDevice
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:noGamepadsConnectedAtAllSaysSo()]
removed call to com/rox/input/ControllerConfigLoader::warnMissing → KILLED

204

1.1
Location : findDevice
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceSelectsDeviceByExplicitIndex()]
replaced return value with Optional.empty for com/rox/input/ControllerConfigLoader::findDevice → KILLED

208

1.1
Location : warnMissing
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:noGamepadsConnectedAtAllSaysSo()]
negated conditional → KILLED

210

1.1
Location : lambda$warnMissing$0
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceWithNoMatchingDeviceNameWarnsAndStartsThatPlayerWithNoController()]
replaced return value with "" for com/rox/input/ControllerConfigLoader::lambda$warnMissing$0 → KILLED

218

1.1
Location : parseGamepadBindings
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:aGamepadBindingTypoIsStillAnErrorWhenTheGamepadIsMissing()]
negated conditional → KILLED

222

1.1
Location : parseGamepadBindings
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceDefaultsToDeviceIndexZeroAndBuildsAWorkingGamepadController()]
replaced return value with Collections.emptyMap for com/rox/input/ControllerConfigLoader::parseGamepadBindings → KILLED

229

1.1
Location : resolveGamepadBinding
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:aGamepadBindingTypoIsStillAnErrorWhenTheGamepadIsMissing()]
negated conditional → KILLED

233

1.1
Location : resolveGamepadBinding
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadButtonBindingWithADirectionSuffixThrows()]
negated conditional → KILLED

234

1.1
Location : resolveGamepadBinding
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadDeviceSupplierIsOnlyCalledOnceAcrossMultipleGamepadPlayers()]
changed conditional boundary → KILLED

2.2
Location : resolveGamepadBinding
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadButtonBindingWithADirectionSuffixThrows()]
negated conditional → KILLED

239

1.1
Location : resolveGamepadBinding
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadSourceSelectsDeviceByExplicitIndex()]
replaced return value with null for com/rox/input/ControllerConfigLoader::resolveGamepadBinding → KILLED

241

1.1
Location : resolveGamepadBinding
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadAxisBindingWithoutADirectionSuffixThrows()]
negated conditional → KILLED

2.2
Location : resolveGamepadBinding
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadAxisBindingWithANegativeDirectionOnlyTriggersOnANegativeValue()]
changed conditional boundary → KILLED

253

1.1
Location : resolveGamepadBinding
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:gamepadAxisBindingWithANegativeDirectionOnlyTriggersOnANegativeValue()]
replaced return value with null for com/rox/input/ControllerConfigLoader::resolveGamepadBinding → KILLED

260

1.1
Location : parseKeyBindings
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:unknownKeyNameThrowsAClearException()]
negated conditional → KILLED

264

1.1
Location : parseKeyBindings
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:keyboardSourceBuildsAWorkingKeyboardControllerFromItsBindings()]
replaced return value with Collections.emptyMap for com/rox/input/ControllerConfigLoader::parseKeyBindings → KILLED

271

1.1
Location : resolveKeyCode
Killed by : com.rox.input.ControllerConfigLoaderTest.[engine:junit-jupiter]/[class:com.rox.input.ControllerConfigLoaderTest]/[method:keyboardSourceBuildsAWorkingKeyboardControllerFromItsBindings()]
replaced int return with 0 for com/rox/input/ControllerConfigLoader::resolveKeyCode → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1