diff --git a/pitest-command-line/src/main/java/org/pitest/mutationtest/commandline/CommaAwareArgsProcessor.java b/pitest-command-line/src/main/java/org/pitest/mutationtest/commandline/CommaAwareArgsProcessor.java new file mode 100644 index 000000000..ee01d8c0f --- /dev/null +++ b/pitest-command-line/src/main/java/org/pitest/mutationtest/commandline/CommaAwareArgsProcessor.java @@ -0,0 +1,92 @@ +package org.pitest.mutationtest.commandline; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * JVM args preprocessor to allow single arguments with commas. + * The processor will first replace all commas with '@' if the comma is within a region of the argument marked by { and }. + * The processor will then split up the arguments by commas that are outside the markers. + * As a final step the '@' are replaced with commas again. + */ +public class CommaAwareArgsProcessor { + + private static final char REGION_BEGIN = '{'; + private static final char REGION_END = '}'; + private final OptionSpec optionsSpec; + + public CommaAwareArgsProcessor(OptionSpec args) { + this.optionsSpec = args; + } + + public List values(OptionSet userArgs) { + final String commandLineOption = optionsSpec.value(userArgs); + if (commandLineOption == null) { + return Collections.emptyList(); + } + + Set modifiedIndices = new HashSet<>(); + String preprocessedOptions = replaceCommas(commandLineOption, modifiedIndices); + + String[] arguments = preprocessedOptions.split(","); + return postProcess(modifiedIndices, arguments); + } + + /** + * Put commas back and delete region marker. + */ + private List postProcess(Set modifiedIndices, String[] arguments) { + List newArguments = new ArrayList<>(); + int base = 0; + for (String argument : arguments) { + newArguments.add(buildNewArgument(modifiedIndices, base, argument).toString()); + base += argument.length() + 1; + } + return newArguments; + } + + private StringBuilder buildNewArgument(Set modifiedIndices, int base, String argument) { + StringBuilder newArgument = new StringBuilder(); + for (int j = 0; j < argument.length(); j++) { + char current = argument.charAt(j); + + // Only remove region markers, if commas have been replaced. Otherwise treat them as part of the argument. + if (!modifiedIndices.isEmpty() && (current == REGION_BEGIN || current == REGION_END)) { + continue; + } + if (current == '@' && modifiedIndices.contains(j + base)) { + newArgument.append(','); + } else { + newArgument.append(current); + } + } + return newArgument; + } + + private String replaceCommas(String single, Set modifiedIndices) { + StringBuilder newString = new StringBuilder(); + boolean inSpecialRegion = false; + for (int i = 0; i < single.length(); i++) { + char current = single.charAt(i); + char tobeAdded = current; + if (current == REGION_BEGIN && !inSpecialRegion) { + inSpecialRegion = true; + } else if (current == REGION_END && inSpecialRegion) { + inSpecialRegion = false; + } else if (inSpecialRegion && current == ',') { + tobeAdded = '@'; + modifiedIndices.add(i); + } + newString.append(tobeAdded); + } + return newString.toString(); + } + + +} diff --git a/pitest-command-line/src/main/java/org/pitest/mutationtest/commandline/OptionsParser.java b/pitest-command-line/src/main/java/org/pitest/mutationtest/commandline/OptionsParser.java index a4019c0ef..145fb6902 100644 --- a/pitest-command-line/src/main/java/org/pitest/mutationtest/commandline/OptionsParser.java +++ b/pitest-command-line/src/main/java/org/pitest/mutationtest/commandline/OptionsParser.java @@ -107,6 +107,7 @@ public class OptionsParser { private final OptionSpec mutators; private final OptionSpec features; private final OptionSpec jvmArgs; + private final CommaAwareArgsProcessor jvmArgsProcessor; private final OptionSpec timeoutFactorSpec; private final OptionSpec timeoutConstSpec; private final OptionSpec excludedMethodsSpec; @@ -205,9 +206,10 @@ public OptionsParser(Predicate dependencyFilter) { .describedAs("comma separated list of features to enable/disable."); this.jvmArgs = parserAccepts(CHILD_JVM).withRequiredArg() - .withValuesSeparatedBy(',') .describedAs("comma separated list of child JVM args"); + this.jvmArgsProcessor = new CommaAwareArgsProcessor(jvmArgs); + this.detectInlinedCode = parserAccepts(USE_INLINED_CODE_DETECTION) .withOptionalArg() .ofType(Boolean.class) @@ -408,7 +410,7 @@ private ParseResult parseCommandLine(final ReportOptions data, data.setMutators(this.mutators.values(userArgs)); data.setFeatures(this.features.values(userArgs)); data.setDependencyAnalysisMaxDistance(this.depth.value(userArgs)); - data.addChildJVMArgs(this.jvmArgs.values(userArgs)); + data.addChildJVMArgs(this.jvmArgsProcessor.values(userArgs)); data.setFullMutationMatrix(this.fullMutationMatrixSpec.value(userArgs)); diff --git a/pitest-command-line/src/test/java/org/pitest/mutationtest/commandline/CommaAwareArgsProcessorTest.java b/pitest-command-line/src/test/java/org/pitest/mutationtest/commandline/CommaAwareArgsProcessorTest.java new file mode 100644 index 000000000..a978f5552 --- /dev/null +++ b/pitest-command-line/src/test/java/org/pitest/mutationtest/commandline/CommaAwareArgsProcessorTest.java @@ -0,0 +1,59 @@ +package org.pitest.mutationtest.commandline; + +import joptsimple.ArgumentAcceptingOptionSpec; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.pitest.mutationtest.config.ConfigOption; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +@RunWith(Parameterized.class) +public class CommaAwareArgsProcessorTest { + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { + { Arrays.asList("--unrelated", "a"), Collections.emptyList() }, + { Arrays.asList("--jvmArgs", "a"), Arrays.asList("a") }, + { Arrays.asList("--jvmArgs", "a,b,c,d"), Arrays.asList("a","b","c","d") }, + { Arrays.asList("--jvmArgs", "a;b,c;d"), Arrays.asList("a;b","c;d") }, + { Arrays.asList("--jvmArgs", "{a,b,c,d}"), Arrays.asList("a,b,c,d") }, + { Arrays.asList("--jvmArgs", "{a;b;c;d}"), Arrays.asList("{a;b;c;d}") }, // no separators so region markers will not be removed + { Arrays.asList("--jvmArgs", "{a,b},{c,d},{e,f}"), Arrays.asList("a,b","c,d","e,f") }, + { Arrays.asList("--jvmArgs", "{a,b@c,d}"), Arrays.asList("a,b@c,d") }, // pre-existing '@' will not be replaced with "," + }); + } + + private final List input; + private final List expected; + + + public CommaAwareArgsProcessorTest(List input, List expected) { + this.input = input; + this.expected = expected; + } + + @Test + public void parseArgs() { + OptionParser optionParser = new OptionParser(); + ArgumentAcceptingOptionSpec args = optionParser + .accepts(ConfigOption.CHILD_JVM.getParamName()) + .withRequiredArg() + .describedAs("jvm args for child JVM"); + ArgumentAcceptingOptionSpec unrelatedArgs = optionParser + .accepts("unrelated") + .withRequiredArg() + .describedAs("unrelatedArgs"); + + final OptionSet userArgs = optionParser.parse(this.input.toArray(new String[0])); + Assert.assertEquals(this.expected, new CommaAwareArgsProcessor(args).values(userArgs)); + } + +}