/* * Copyright 2016 ThoughtWorks, Inc. * * Licensed 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 com.thoughtworks.go.util.command; import com.googlecode.junit.ext.JunitExtRunner; import com.googlecode.junit.ext.RunIf; import com.googlecode.junit.ext.checkers.OSChecker; import com.thoughtworks.go.junitext.EnhancedOSChecker; import com.thoughtworks.go.util.*; import org.apache.log4j.Level; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.HashMap; import java.util.regex.Matcher; import static com.thoughtworks.go.junitext.EnhancedOSChecker.DO_NOT_RUN_ON; import static com.thoughtworks.go.util.LogFixture.logFixtureFor; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @RunWith(JunitExtRunner.class) public class CommandLineTest { private static final String DBL_QUOTE = "\""; private static final String EXEC_WITH_SPACES = "dummyExecutable with spaces"; private static final String ARG_SPACES_NOQUOTES = "arg1='spaced single quoted value'"; private static final String ARG_NOSPACES = "arg2=value2"; private static final String ARG_SPACES = "arg3=value for 3"; private File subFolder; @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @Before public void setUp() throws Exception { // Have to call this as it uses another Junit runner which overrides the rule temporaryFolder.create(); subFolder = temporaryFolder.newFolder("subFolder"); File file = temporaryFolder.newFile("./originalCommand"); file.setExecutable(true); } @After public void tearDown() throws Exception { temporaryFolder.delete(); } @Test public void testToStringWithSeparator() throws Exception { final String separator = "], ["; assertEquals("", CommandLine.toString(null, false, separator)); assertEquals(ARG_SPACES_NOQUOTES, CommandLine.toString(new String[]{ARG_SPACES_NOQUOTES}, false, separator)); assertEquals(ARG_SPACES_NOQUOTES + separator + ARG_NOSPACES, CommandLine.toString(new String[]{ARG_SPACES_NOQUOTES, ARG_NOSPACES}, false, separator)); assertEquals(ARG_SPACES_NOQUOTES + separator + ARG_NOSPACES + separator + ARG_SPACES, CommandLine.toString(new String[]{ARG_SPACES_NOQUOTES, ARG_NOSPACES, ARG_SPACES}, false, separator)); } @Test public void testToStrings() throws Exception { final CommandLine cl = CommandLine.createCommandLine(EXEC_WITH_SPACES); cl.withArg(ARG_SPACES_NOQUOTES); cl.withArg(ARG_NOSPACES); cl.withArg(ARG_SPACES); final String expectedWithQuotes = DBL_QUOTE + EXEC_WITH_SPACES + DBL_QUOTE + " " + DBL_QUOTE + ARG_SPACES_NOQUOTES + DBL_QUOTE + " " + ARG_NOSPACES + " " + DBL_QUOTE + ARG_SPACES + DBL_QUOTE; assertEquals(expectedWithQuotes, cl.toString()); assertEquals(expectedWithQuotes.replaceAll(DBL_QUOTE, ""), cl.toStringForDisplay()); assertEquals("Did the impl of CommandLine.toString() change?", expectedWithQuotes, cl + ""); } @Test public void testToStringMisMatchedQuote() { final CommandLine cl2 = CommandLine.createCommandLine(EXEC_WITH_SPACES); final String argWithMismatchedDblQuote = "argMisMatch='singlequoted\"WithMismatchedDblQuote'"; cl2.withArg(argWithMismatchedDblQuote); assertEquals("Should escape double quotes inside the string", DBL_QUOTE + EXEC_WITH_SPACES + DBL_QUOTE + " " + DBL_QUOTE + argWithMismatchedDblQuote.replaceAll("\"", Matcher.quoteReplacement("\\\"")) + DBL_QUOTE, cl2.toString()); } @Test public void shouldReportPasswordsOnTheLogAsStars() { CommandLine line = CommandLine.createCommandLine("notexist").withArg(new PasswordArgument("secret")); assertThat(line.toString(), not(containsString("secret"))); } @Test public void shouldLogPasswordsOnTheLogAsStars() { try (LogFixture logFixture = logFixtureFor(ProcessManager.class, Level.DEBUG)) { CommandLine line = CommandLine.createCommandLine("notexist").withArg(new PasswordArgument("secret")); try { line.runOrBomb(null); } catch (Exception e) { //ignored } assertThat(ArrayUtil.join(logFixture.getMessages()), containsString("notexist ******")); } } @Test @RunIf(value = EnhancedOSChecker.class, arguments = {DO_NOT_RUN_ON, OSChecker.WINDOWS}) public void shouldNotLogPasswordsFromStream() { try (LogFixture logFixture = logFixtureFor(CommandLine.class, Level.DEBUG)) { CommandLine line = CommandLine.createCommandLine("/bin/echo").withArg("=>").withArg(new PasswordArgument("secret")); line.runOrBomb(null); System.out.println(ArrayUtil.join(logFixture.getMessages())); assertThat(ArrayUtil.join(logFixture.getMessages()), not(containsString("secret"))); assertThat(ArrayUtil.join(logFixture.getMessages()), containsString("=> ******")); } } @Test @RunIf(value = EnhancedOSChecker.class, arguments = {DO_NOT_RUN_ON, OSChecker.WINDOWS}) public void shouldNotLogPasswordsOnExceptionThrown() throws IOException { File dir = FileUtil.createTempFolder(); File file = new File(dir, "test.sh"); FileOutputStream out = new FileOutputStream(file); out.write("echo $1 && exit 10".getBytes()); out.close(); CommandLine line = CommandLine.createCommandLine("/bin/sh").withArg(file.getAbsolutePath()).withArg(new PasswordArgument("secret")); try { line.runOrBomb(null); } catch (CommandLineException e) { assertThat(e.getMessage(), not(containsString("secret"))); } } @Test @RunIf(value = EnhancedOSChecker.class, arguments = {DO_NOT_RUN_ON, OSChecker.WINDOWS}) public void shouldLogPasswordsOnOutputAsStarsUnderLinux() throws IOException { CommandLine line = CommandLine.createCommandLine("echo") .withArg("My Password is:") .withArg(new PasswordArgument("secret")); InMemoryStreamConsumer output = new InMemoryStreamConsumer(); InMemoryStreamConsumer displayOutputStreamConsumer = InMemoryStreamConsumer.inMemoryConsumer(); ProcessWrapper processWrapper = line.execute(output, new EnvironmentVariableContext(), null); processWrapper.waitForExit(); assertThat(output.getAllOutput(), containsString("secret")); assertThat(displayOutputStreamConsumer.getAllOutput(), not(containsString("secret"))); } @Test @RunIf(value = OSChecker.class, arguments = OSChecker.WINDOWS) public void shouldLogPasswordsOnOutputAsStarsUnderWindows() throws IOException { CommandLine line = CommandLine.createCommandLine("cmd") .withArg("/c") .withArg("echo") .withArg("My Password is:") .withArg(new PasswordArgument("secret")); InMemoryStreamConsumer output = new InMemoryStreamConsumer(); InMemoryStreamConsumer displayOutputStreamConsumer = InMemoryStreamConsumer.inMemoryConsumer(); ProcessWrapper processWrapper = line.execute(output, new EnvironmentVariableContext(), null); processWrapper.waitForExit(); assertThat(output.getAllOutput(), containsString("secret")); assertThat(displayOutputStreamConsumer.getAllOutput(), not(containsString("secret"))); } @Test public void shouldShowPasswordsInToStringForDisplayAsStars() throws IOException { CommandLine line = CommandLine.createCommandLine("echo") .withArg("My Password is:") .withArg(new PasswordArgument("secret")); assertThat(line.toStringForDisplay(), not(containsString("secret"))); } @Test public void shouldShowPasswordsInDescribeAsStars() throws IOException { HashMap<String, String> map = new HashMap<>(); map.put("password1", "secret"); map.put("password2", "secret"); CommandLine line = CommandLine.createCommandLine("echo") .withArg("My Password is:") .withEnv(map) .withArg(new PasswordArgument("secret")) .withArg(new PasswordArgument("new-pwd")); line.addInput(new String[]{"my pwd is: new-pwd "}); assertThat(line.describe(), not(containsString("secret"))); assertThat(line.describe(), not(containsString("new-pwd"))); } @Test @RunIf(value = EnhancedOSChecker.class, arguments = {DO_NOT_RUN_ON, OSChecker.WINDOWS}) public void shouldLogPasswordsOnEnvironemntAsStarsUnderLinux() throws IOException { CommandLine line = CommandLine.createCommandLine("echo") .withArg("My Password is:") .withArg("secret") .withArg(new PasswordArgument("secret")); EnvironmentVariableContext environmentVariableContext = new EnvironmentVariableContext(); environmentVariableContext.setProperty("ENV_PASSWORD", "secret", false); InMemoryStreamConsumer output = new InMemoryStreamConsumer(); InMemoryStreamConsumer forDisplay = InMemoryStreamConsumer.inMemoryConsumer(); ProcessWrapper processWrapper = line.execute(output, environmentVariableContext, null); processWrapper.waitForExit(); assertThat(forDisplay.getAllOutput(), not(containsString("secret"))); assertThat(output.getAllOutput(), containsString("secret")); } @Test @RunIf(value = EnhancedOSChecker.class, arguments = {DO_NOT_RUN_ON, OSChecker.WINDOWS}) public void shouldBeAbleToSpecifyEncoding() throws IOException { String chrisWasHere = "?????"; CommandLine line = CommandLine.createCommandLine("echo") .withArg(chrisWasHere) .withEncoding("UTF-8"); InMemoryStreamConsumer output = new InMemoryStreamConsumer(); ProcessWrapper processWrapper = line.execute(output, new EnvironmentVariableContext(), null); processWrapper.waitForExit(); assertThat(output.getAllOutput(), containsString(chrisWasHere)); } @Test @RunIf(value = EnhancedOSChecker.class, arguments = {DO_NOT_RUN_ON, OSChecker.WINDOWS}) public void shouldBeAbleToRunCommandsInSubdirectories() throws IOException { File shellScript = createScript("hello-world.sh", "echo ${PWD}"); assertThat(shellScript.setExecutable(true), is(true)); CommandLine line = CommandLine.createCommandLine("./hello-world.sh").withWorkingDir(subFolder); InMemoryStreamConsumer out = new InMemoryStreamConsumer(); line.execute(out, new EnvironmentVariableContext(), null).waitForExit(); assertThat(out.getAllOutput().trim(), endsWith("subFolder")); } @Test @RunIf(value = EnhancedOSChecker.class, arguments = {DO_NOT_RUN_ON, OSChecker.WINDOWS}) public void shouldBeAbleToRunCommandsInSubdirectoriesWithNoWorkingDir() throws IOException { File shellScript = createScript("hello-world.sh", "echo 'Hello World!'"); assertThat(shellScript.setExecutable(true), is(true)); CommandLine line = CommandLine.createCommandLine("subFolder/hello-world.sh").withWorkingDir(temporaryFolder.getRoot()); InMemoryStreamConsumer out = new InMemoryStreamConsumer(); line.execute(out, new EnvironmentVariableContext(), null).waitForExit(); assertThat(out.getAllOutput(), containsString("Hello World!")); } @Test @RunIf(value = EnhancedOSChecker.class, arguments = {DO_NOT_RUN_ON, OSChecker.WINDOWS}) public void shouldNotRunLocalCommandsThatAreNotExecutable() throws IOException { createScript("echo", "echo 'this should not be here'"); CommandLine line = CommandLine.createCommandLine("echo") .withArg("Using the REAL echo") .withWorkingDir(subFolder); InMemoryStreamConsumer out = new InMemoryStreamConsumer(); line.execute(out, new EnvironmentVariableContext(), null).waitForExit(); assertThat(out.getAllOutput(), containsString("Using the REAL echo")); } @Test @RunIf(value = EnhancedOSChecker.class, arguments = {DO_NOT_RUN_ON, OSChecker.WINDOWS}) public void shouldBeAbleToRunCommandsFromRelativeDirectories() throws IOException { File shellScript = temporaryFolder.newFile("hello-world.sh"); FileUtil.writeContentToFile("echo ${PWD}", shellScript); assertThat(shellScript.setExecutable(true), is(true)); CommandLine line = CommandLine.createCommandLine("../hello-world.sh").withWorkingDir(subFolder); InMemoryStreamConsumer out = new InMemoryStreamConsumer(); line.execute(out, new EnvironmentVariableContext(), null).waitForExit(); assertThat(out.getAllOutput().trim(), endsWith("subFolder")); } private File createScript(String name, String content) throws IOException { File shellScript = new File(subFolder, name); FileUtil.writeContentToFile(content, shellScript); return shellScript; } @Test public void shouldReturnEchoResult() throws Exception { if (SystemUtil.isWindows()) { ConsoleResult result = CommandLine.createCommandLine("cmd").runOrBomb(null); assertThat(result.outputAsString(), containsString("Windows")); } else { String expectedValue = "my input"; ConsoleResult result = CommandLine.createCommandLine("echo").withArgs(expectedValue).runOrBomb(null); assertThat(result.outputAsString(), is(expectedValue)); } } @Test(expected = Exception.class) public void shouldReturnThrowExceptionWhenCommandNotExist() throws Exception { CommandLine.createCommandLine("something").runOrBomb(null); } @Test public void shouldGetTheCommandFromCommandlineAsIs() throws IOException { String file = "originalCommand"; CommandLine command = CommandLine.createCommandLine(file); command.setWorkingDir(new File(".")); String[] commandLineArgs = command.getCommandLine(); assertThat(commandLineArgs[0], is(file)); } @Test public void shouldPrefixStderrOutput() { CommandLine line = CommandLine.createCommandLine("rmdir").withArg("/a/directory/that/does/not/exist"); InMemoryStreamConsumer output = new InMemoryStreamConsumer(); ProcessWrapper processWrapper = line.execute(output, new EnvironmentVariableContext(), null); processWrapper.waitForExit(); assertThat(output.getAllOutput(), containsString("STDERR: ")); } }