GEODE-3103: GfshRule no longer clutters output - GfshRule writes via a logger rather than StdOut. This will make it no longer clutter precheckin runs or the nightly build. - Introduce ProcessLogger to capture output from the Gfsh JVM so that tests can assert against the output.
Project: http://git-wip-us.apache.org/repos/asf/geode/repo Commit: http://git-wip-us.apache.org/repos/asf/geode/commit/f63b9d14 Tree: http://git-wip-us.apache.org/repos/asf/geode/tree/f63b9d14 Diff: http://git-wip-us.apache.org/repos/asf/geode/diff/f63b9d14 Branch: refs/heads/feature/GEODE-3109 Commit: f63b9d145eee93f989998c9f36b3f2b7a85ce07e Parents: b0fafd3 Author: Jared Stewart <jstew...@pivotal.io> Authored: Wed Jun 21 13:21:56 2017 -0700 Committer: Jared Stewart <jstew...@pivotal.io> Committed: Wed Jun 28 13:59:45 2017 -0700 ---------------------------------------------------------------------- geode-assembly/build.gradle | 1 + .../cli/commands/StatusLocatorRealGfshTest.java | 22 ++--- .../test/dunit/rules/gfsh/GfshExecution.java | 50 +++++++++++ .../geode/test/dunit/rules/gfsh/GfshRule.java | 75 ++++++++++------ .../geode/test/dunit/rules/gfsh/GfshScript.java | 48 +++++++---- .../test/dunit/rules/gfsh/ProcessLogger.java | 90 ++++++++++++++++++++ .../test/dunit/rules/gfsh/StreamGobbler.java | 38 +++++++++ .../internal/cli/util/CommandStringBuilder.java | 31 +++++-- 8 files changed, 292 insertions(+), 63 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/geode/blob/f63b9d14/geode-assembly/build.gradle ---------------------------------------------------------------------- diff --git a/geode-assembly/build.gradle b/geode-assembly/build.gradle index 39bb542..3bfe57e 100755 --- a/geode-assembly/build.gradle +++ b/geode-assembly/build.gradle @@ -72,6 +72,7 @@ dependencies { testCompile 'org.apache.httpcomponents:httpclient:' + project.'httpclient.version' testCompile 'org.apache.httpcomponents:httpcore:' + project.'httpcore.version' + testCompile 'com.google.guava:guava:' + project.'guava.version' testRuntime files("${System.getProperty('java.home')}/../lib/tools.jar") testRuntime files("$buildDir/install/${distributions.main.baseName}/lib/geode-dependencies.jar") http://git-wip-us.apache.org/repos/asf/geode/blob/f63b9d14/geode-assembly/src/test/java/org/apache/geode/management/internal/cli/commands/StatusLocatorRealGfshTest.java ---------------------------------------------------------------------- diff --git a/geode-assembly/src/test/java/org/apache/geode/management/internal/cli/commands/StatusLocatorRealGfshTest.java b/geode-assembly/src/test/java/org/apache/geode/management/internal/cli/commands/StatusLocatorRealGfshTest.java index 82ee240..55f1a9c 100644 --- a/geode-assembly/src/test/java/org/apache/geode/management/internal/cli/commands/StatusLocatorRealGfshTest.java +++ b/geode-assembly/src/test/java/org/apache/geode/management/internal/cli/commands/StatusLocatorRealGfshTest.java @@ -14,16 +14,13 @@ */ package org.apache.geode.management.internal.cli.commands; -import org.apache.geode.test.dunit.rules.gfsh.GfshRule; -import org.apache.geode.test.dunit.rules.gfsh.GfshScript; -import org.apache.geode.test.junit.categories.DistributedTest; -import org.apache.geode.test.junit.categories.IntegrationTest; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; -import java.io.File; -import java.util.concurrent.TimeUnit; +import org.apache.geode.test.dunit.rules.gfsh.GfshRule; +import org.apache.geode.test.dunit.rules.gfsh.GfshScript; +import org.apache.geode.test.junit.categories.DistributedTest; @Category(DistributedTest.class) public class StatusLocatorRealGfshTest { @@ -32,19 +29,16 @@ public class StatusLocatorRealGfshTest { @Test public void statusLocatorSucceedsWhenConnected() throws Exception { - gfshRule.execute(GfshScript.of("start locator --name=locator1").awaitAtMost(1, TimeUnit.MINUTES) - .expectExitCode(0)); + GfshScript.of("start locator --name=locator1").execute(gfshRule); - gfshRule.execute(GfshScript.of("connect", "status locator --name=locator1") - .awaitAtMost(1, TimeUnit.MINUTES).expectExitCode(0)); + GfshScript.of("connect", "status locator --name=locator1").execute(gfshRule); } @Test public void statusLocatorFailsWhenNotConnected() throws Exception { - gfshRule.execute(GfshScript.of("start locator --name=locator1").awaitAtMost(1, TimeUnit.MINUTES) - .expectExitCode(0)); + GfshScript.of("start locator --name=locator1").withName("start locator").execute(gfshRule); - gfshRule.execute(GfshScript.of("status locator --name=locator1") - .awaitAtMost(1, TimeUnit.MINUTES).expectExitCode(1)); + GfshScript.of("status locator --name=locator1").withName("status locator").expectFailure() + .execute(gfshRule); } } http://git-wip-us.apache.org/repos/asf/geode/blob/f63b9d14/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshExecution.java ---------------------------------------------------------------------- diff --git a/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshExecution.java b/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshExecution.java new file mode 100644 index 0000000..23f2a73 --- /dev/null +++ b/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshExecution.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.test.dunit.rules.gfsh; + +import java.io.File; +import java.util.List; + +public class GfshExecution { + private final Process process; + private final File workingDir; + private final ProcessLogger processLogger; + + protected GfshExecution(Process process, File workingDir) { + this.process = process; + this.workingDir = workingDir; + this.processLogger = new ProcessLogger(process, workingDir.getName()); + } + + public List<String> getStdOutLines() { + return processLogger.getStdOutLines(); + } + + public List<String> getStdErrLines() { + return processLogger.getStdErrLines(); + } + + /** + * Note that this is the working directory of gfsh itself. If your script started a server or + * locator, this will be the parent directory of that member's working directory. + */ + public File getWorkingDir() { + return workingDir; + } + + public Process getProcess() { + return this.process; + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/f63b9d14/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshRule.java ---------------------------------------------------------------------- diff --git a/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshRule.java b/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshRule.java index 8109377..fc6f376 100644 --- a/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshRule.java +++ b/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshRule.java @@ -24,11 +24,13 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.junit.rules.ExternalResource; import org.junit.rules.TemporaryFolder; import org.apache.geode.management.internal.cli.commands.StatusLocatorRealGfshTest; +import org.apache.geode.management.internal.cli.util.CommandStringBuilder; import org.apache.geode.test.dunit.rules.RequiresGeodeHome; /** @@ -39,25 +41,26 @@ import org.apache.geode.test.dunit.rules.RequiresGeodeHome; */ public class GfshRule extends ExternalResource { private TemporaryFolder temporaryFolder = new TemporaryFolder(); - private List<Process> processes = new ArrayList<>(); + private List<GfshExecution> gfshExecutions; private Path gfsh; - public Process execute(String... commands) { + public GfshExecution execute(String... commands) { return execute(GfshScript.of(commands)); } - public Process execute(GfshScript gfshScript) { - Process process; + protected GfshExecution execute(GfshScript gfshScript) { + GfshExecution gfshExecution; try { - process = gfshScript.toProcessBuilder(gfsh, temporaryFolder.getRoot()).start(); + File workingDir = temporaryFolder.newFolder(gfshScript.getName()); + Process process = toProcessBuilder(gfshScript, gfsh, workingDir).start(); + gfshExecution = new GfshExecution(process, workingDir); + gfshExecutions.add(gfshExecution); + gfshScript.awaitIfNecessary(process); } catch (IOException e) { throw new RuntimeException(e); } - processes.add(process); - gfshScript.awaitIfNecessary(process); - - return process; + return gfshExecution; } @Override @@ -65,6 +68,7 @@ public class GfshRule extends ExternalResource { gfsh = new RequiresGeodeHome().getGeodeHome().toPath().resolve("bin/gfsh"); assertThat(gfsh).exists(); + gfshExecutions = new ArrayList<>(); temporaryFolder.create(); } @@ -74,21 +78,35 @@ public class GfshRule extends ExternalResource { */ @Override protected void after() { - stopMembersQuietly(); - processes.forEach(Process::destroyForcibly); - processes.forEach((Process process) -> { - try { - // Process.destroyForcibly() may not terminate immediately - process.waitFor(1, TimeUnit.MINUTES); - } catch (InterruptedException ignore) { - // We ignore this exception so that we still attempt the rest of the cleanup. - } - }); + gfshExecutions.stream().map(GfshExecution::getWorkingDir).collect(Collectors.toList()) + .forEach(this::stopMembersQuietly); + + gfshExecutions.stream().map(GfshExecution::getProcess).map(Process::destroyForcibly) + .forEach((Process process) -> { + try { + // Process.destroyForcibly() may not terminate immediately + process.waitFor(1, TimeUnit.MINUTES); + } catch (InterruptedException ignore) { + // We ignore this exception so that we still attempt the rest of the cleanup. + } + }); + temporaryFolder.delete(); } - private void stopMembersQuietly() { - File[] directories = temporaryFolder.getRoot().listFiles(File::isDirectory); + protected ProcessBuilder toProcessBuilder(GfshScript gfshScript, Path gfshPath, File workingDir) { + List<String> commandsToExecute = new ArrayList<>(); + commandsToExecute.add(gfshPath.toAbsolutePath().toString()); + + for (String command : gfshScript.getCommands()) { + commandsToExecute.add("-e " + command); + } + + return new ProcessBuilder(commandsToExecute).directory(workingDir); + } + + private void stopMembersQuietly(File parentDirectory) { + File[] potentalMemberDirectories = parentDirectory.listFiles(File::isDirectory); Predicate<File> isServerDir = (File directory) -> Arrays.stream(directory.list()) .anyMatch(filename -> filename.endsWith("server.pid")); @@ -96,21 +114,24 @@ public class GfshRule extends ExternalResource { Predicate<File> isLocatorDir = (File directory) -> Arrays.stream(directory.list()) .anyMatch(filename -> filename.endsWith("locator.pid")); - Arrays.stream(directories).filter(isServerDir).forEach(this::stopServerInDir); - Arrays.stream(directories).filter(isLocatorDir).forEach(this::stopLocatorInDir); + Arrays.stream(potentalMemberDirectories).filter(isServerDir).forEach(this::stopServerInDir); + Arrays.stream(potentalMemberDirectories).filter(isLocatorDir).forEach(this::stopLocatorInDir); } private void stopServerInDir(File dir) { - GfshScript stopServerScript = new GfshScript("stop server --dir=" + dir.getAbsolutePath()) - .awaitQuietlyAtMost(1, TimeUnit.MINUTES); + String stopServerCommand = + new CommandStringBuilder("stop server").addOption("dir", dir).toString(); + GfshScript stopServerScript = new GfshScript(stopServerCommand).awaitQuietly(); execute(stopServerScript); } private void stopLocatorInDir(File dir) { - GfshScript stopServerScript = new GfshScript("stop locator --dir=" + dir.getAbsolutePath()) - .awaitQuietlyAtMost(1, TimeUnit.MINUTES); + String stopLocatorCommand = + new CommandStringBuilder("stop locator").addOption("dir", dir).toString(); + GfshScript stopServerScript = new GfshScript(stopLocatorCommand).awaitQuietly(); execute(stopServerScript); } + } http://git-wip-us.apache.org/repos/asf/geode/blob/f63b9d14/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshScript.java ---------------------------------------------------------------------- diff --git a/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshScript.java b/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshScript.java index 3ee1402..30c7140 100644 --- a/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshScript.java +++ b/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/GfshScript.java @@ -16,25 +16,33 @@ package org.apache.geode.test.dunit.rules.gfsh; import static org.assertj.core.api.Assertions.assertThat; -import java.io.File; -import java.nio.file.Path; import java.util.concurrent.TimeUnit; +import org.apache.geode.management.internal.cli.util.ThreePhraseGenerator; + public class GfshScript { private final String[] commands; - private Integer timeout; - private TimeUnit timeoutTimeUnit; + private String name = new ThreePhraseGenerator().generate('-'); + private TimeUnit timeoutTimeUnit = TimeUnit.MINUTES; + private int timeout = 1; private boolean awaitQuietly = false; - private Integer expectedExitValue; + private int expectedExitValue = 0; public GfshScript(String... commands) { this.commands = commands; } + /** + * By default, this GfshScript will await at most 2 minutes and will expect success. + */ public static GfshScript of(String... commands) { return new GfshScript(commands); } + public GfshScript withName(String name) { + this.name = name; + return this; + } public GfshScript expectExitCode(int expectedExitCode) { this.expectedExitValue = expectedExitCode; @@ -42,6 +50,10 @@ public class GfshScript { return this; } + public GfshScript expectFailure() { + return expectExitCode(1); + } + /** * Will cause the thread that executes {@link GfshScript#awaitIfNecessary} to wait, if necessary, * until the subprocess executing this Gfsh script has terminated, or the specified waiting time @@ -68,16 +80,14 @@ public class GfshScript { return awaitAtMost(timeout, timeUnit); } + public GfshScript awaitQuietly() { + this.awaitQuietly = true; - protected ProcessBuilder toProcessBuilder(Path gfshPath, File workingDir) { - String[] gfshCommands = new String[commands.length + 1]; - gfshCommands[0] = gfshPath.toAbsolutePath().toString(); - - for (int i = 0; i < commands.length; i++) { - gfshCommands[i + 1] = "-e " + commands[i]; - } + return this; + } - return new ProcessBuilder(gfshCommands).inheritIO().directory(workingDir); + public GfshExecution execute(GfshRule gfshRule) { + return gfshRule.execute(this); } protected void awaitIfNecessary(Process process) { @@ -87,9 +97,7 @@ public class GfshScript { awaitLoudly(process); } - if (expectedExitValue != null) { - assertThat(process.exitValue()).isEqualTo(expectedExitValue); - } + assertThat(process.exitValue()).isEqualTo(expectedExitValue); } private void awaitQuietly(Process process) { @@ -121,4 +129,12 @@ public class GfshScript { private boolean shouldAwaitLoudly() { return shouldAwait() && !awaitQuietly; } + + public String[] getCommands() { + return commands; + } + + public String getName() { + return name; + } } http://git-wip-us.apache.org/repos/asf/geode/blob/f63b9d14/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/ProcessLogger.java ---------------------------------------------------------------------- diff --git a/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/ProcessLogger.java b/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/ProcessLogger.java new file mode 100644 index 0000000..47f0304 --- /dev/null +++ b/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/ProcessLogger.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.test.dunit.rules.gfsh; + +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import com.google.common.collect.Lists; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; + +public class ProcessLogger { + private static final LoggerContext LOGGER_CONTEXT = createLoggerContext(); + private final Logger logger; + + private final Queue<String> stdOutLines = new ConcurrentLinkedQueue<>(); + private final Queue<String> stdErrorLines = new ConcurrentLinkedQueue<>(); + private final StreamGobbler stdOutGobbler; + private final StreamGobbler stdErrGobbler; + + public ProcessLogger(Process process, String name) { + this.logger = LOGGER_CONTEXT.getLogger(name); + + this.stdOutGobbler = new StreamGobbler(process.getInputStream(), this::consumeInfoMessage); + this.stdErrGobbler = new StreamGobbler(process.getErrorStream(), this::consumeErrorMessage); + + stdOutGobbler.startInNewThread(); + stdErrGobbler.startInNewThread(); + } + + private void consumeInfoMessage(String message) { + logger.info(message); + stdOutLines.add(message); + } + + private void consumeErrorMessage(String message) { + logger.error(message); + stdErrorLines.add(message); + } + + private static LoggerContext createLoggerContext() { + ConfigurationBuilder<BuiltConfiguration> builder = + ConfigurationBuilderFactory.newConfigurationBuilder(); + builder.setStatusLevel(Level.ERROR); + builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL) + .addAttribute("level", Level.DEBUG)); + AppenderComponentBuilder appenderBuilder = builder.newAppender("Stdout", "CONSOLE") + .addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT); + appenderBuilder.add(builder.newLayout("PatternLayout").addAttribute("pattern", + "[%-5level %d{HH:mm:ss.SSS z}] (%c): %msg%n%throwable")); + appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL) + .addAttribute("marker", "FLOW")); + builder.add(appenderBuilder); + builder.add(builder.newLogger("org.apache.logging.log4j", Level.ERROR) + .add(builder.newAppenderRef("Stdout")).addAttribute("additivity", false)); + builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout"))); + + return Configurator.initialize(builder.build()); + } + + public List<String> getStdOutLines() { + return Lists.newArrayList(stdOutLines.iterator()); + } + + public List<String> getStdErrLines() { + return Lists.newArrayList(stdOutLines.iterator()); + } + +} http://git-wip-us.apache.org/repos/asf/geode/blob/f63b9d14/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/StreamGobbler.java ---------------------------------------------------------------------- diff --git a/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/StreamGobbler.java b/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/StreamGobbler.java new file mode 100644 index 0000000..e8fd0cc --- /dev/null +++ b/geode-assembly/src/test/java/org/apache/geode/test/dunit/rules/gfsh/StreamGobbler.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.test.dunit.rules.gfsh; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.function.Consumer; + +class StreamGobbler implements Runnable { + private InputStream inputStream; + private Consumer<String> consumeInputLine; + + public StreamGobbler(InputStream inputStream, Consumer<String> consumeInputLine) { + this.inputStream = inputStream; + this.consumeInputLine = consumeInputLine; + } + + public void run() { + new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(consumeInputLine); + } + + public void startInNewThread() { + new Thread(this).start(); + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/f63b9d14/geode-core/src/main/java/org/apache/geode/management/internal/cli/util/CommandStringBuilder.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/util/CommandStringBuilder.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/util/CommandStringBuilder.java index cfb8a24..1f52239 100644 --- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/util/CommandStringBuilder.java +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/util/CommandStringBuilder.java @@ -14,7 +14,10 @@ */ package org.apache.geode.management.internal.cli.util; +import java.io.File; + import org.apache.commons.lang.StringUtils; + import org.apache.geode.internal.lang.SystemUtils; import org.apache.geode.management.internal.cli.GfshParser; @@ -22,15 +25,15 @@ import org.apache.geode.management.internal.cli.GfshParser; /** * - Helper class to build command strings, used in the Dunits for testing gfsh commands * - * * @since GemFire 7.0 */ public class CommandStringBuilder { - private final String OPTION_MARKER = GfshParser.LONG_OPTION_SPECIFIER; - private final String EQUAL_TO = GfshParser.OPTION_VALUE_SPECIFIER; - private final String ARG_SEPARATOR = GfshParser.OPTION_SEPARATOR; - private final String OPTION_SEPARATOR = GfshParser.OPTION_SEPARATOR; - private final String SINGLE_SPACE = " "; + private static final String OPTION_MARKER = GfshParser.LONG_OPTION_SPECIFIER; + private static final String EQUAL_TO = GfshParser.OPTION_VALUE_SPECIFIER; + private static final String ARG_SEPARATOR = GfshParser.OPTION_SEPARATOR; + private static final String OPTION_SEPARATOR = GfshParser.OPTION_SEPARATOR; + private static final String SINGLE_SPACE = " "; + private static final String SINGLE_QUOTE = "\""; private final StringBuffer buffer; private volatile boolean hasOptions; @@ -58,6 +61,10 @@ public class CommandStringBuilder { return this; } + public CommandStringBuilder addOption(String option, File value) { + return addOption(option, quoteArgument(value.getAbsolutePath())); + } + public CommandStringBuilder addOptionWithValueCheck(String option, String value) { if (StringUtils.isNotBlank(value)) { return addOption(option, value); @@ -87,4 +94,16 @@ public class CommandStringBuilder { public String toString() { return getCommandString(); } + + private String quoteArgument(String argument) { + if (!argument.startsWith(SINGLE_QUOTE)) { + argument = SINGLE_QUOTE + argument; + } + + if (!argument.endsWith(SINGLE_QUOTE)) { + argument = argument + SINGLE_QUOTE; + } + + return argument; + } }