blob: 22079fb173a9ae736c369bdd7835aae6e5f3393c [file] [log] [blame]
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.shell;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.shell.ShellUtils.prettyPrintArgv;
import static com.google.devtools.build.lib.shell.ShellUtils.shellEscape;
import static com.google.devtools.build.lib.shell.ShellUtils.tokenize;
import static org.junit.Assert.fail;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for ShellUtils. */
@RunWith(JUnit4.class)
public class ShellUtilsTest {
@Test
public void testShellEscape() throws Exception {
assertThat(shellEscape("")).isEqualTo("''");
assertThat(shellEscape("foo")).isEqualTo("foo");
assertThat(shellEscape("foo bar")).isEqualTo("'foo bar'");
assertThat(shellEscape("'foo'")).isEqualTo("''\\''foo'\\'''");
assertThat(shellEscape("\\'foo\\'")).isEqualTo("'\\'\\''foo\\'\\'''");
assertThat(shellEscape("${filename%.c}.o")).isEqualTo("'${filename%.c}.o'");
assertThat(shellEscape("<html!>")).isEqualTo("'<html!>'");
}
@Test
public void testPrettyPrintArgv() throws Exception {
assertThat(prettyPrintArgv(Arrays.asList("echo", "$US", "100"))).isEqualTo("echo '$US' 100");
}
private void assertTokenize(String copts, String... expectedTokens)
throws Exception {
List<String> actualTokens = new ArrayList<>();
tokenize(actualTokens, copts);
assertThat(actualTokens).isEqualTo(Arrays.asList(expectedTokens));
}
@Test
public void testTokenize() throws Exception {
assertTokenize("-DASMV", "-DASMV");
assertTokenize("-DNO_UNDERLINE", "-DNO_UNDERLINE");
assertTokenize("-DASMV -DNO_UNDERLINE",
"-DASMV", "-DNO_UNDERLINE");
assertTokenize("-DDES_LONG=\"unsigned int\" -wd310",
"-DDES_LONG=unsigned int", "-wd310");
assertTokenize("-Wno-write-strings -Wno-pointer-sign "
+ "-Wno-unused-variable -Wno-pointer-to-int-cast",
"-Wno-write-strings",
"-Wno-pointer-sign",
"-Wno-unused-variable",
"-Wno-pointer-to-int-cast");
}
@Test
public void testTokenizeOnNestedQuotation() throws Exception {
assertTokenize("-Dfoo='foo\"bar' -Dwiz",
"-Dfoo=foo\"bar",
"-Dwiz");
assertTokenize("-Dfoo=\"foo'bar\" -Dwiz",
"-Dfoo=foo'bar",
"-Dwiz");
}
@Test
public void testTokenizeOnBackslashEscapes() throws Exception {
// This would be easier to grok if we forked+exec'd a shell.
assertTokenize("-Dfoo=\\'foo -Dbar", // \' not quoted -> '
"-Dfoo='foo",
"-Dbar");
assertTokenize("-Dfoo=\\\"foo -Dbar", // \" not quoted -> "
"-Dfoo=\"foo",
"-Dbar");
assertTokenize("-Dfoo=\\\\foo -Dbar", // \\ not quoted -> \
"-Dfoo=\\foo",
"-Dbar");
assertTokenize("-Dfoo='\\'foo -Dbar", // \' single quoted -> \, close quote
"-Dfoo=\\foo",
"-Dbar");
assertTokenize("-Dfoo='\\\"foo' -Dbar", // \" single quoted -> \"
"-Dfoo=\\\"foo",
"-Dbar");
assertTokenize("-Dfoo='\\\\foo' -Dbar", // \\ single quoted -> \\
"-Dfoo=\\\\foo",
"-Dbar");
assertTokenize("-Dfoo=\"\\'foo\" -Dbar", // \' double quoted -> \'
"-Dfoo=\\'foo",
"-Dbar");
assertTokenize("-Dfoo=\"\\\"foo\" -Dbar", // \" double quoted -> "
"-Dfoo=\"foo",
"-Dbar");
assertTokenize("-Dfoo=\"\\\\foo\" -Dbar", // \\ double quoted -> \
"-Dfoo=\\foo",
"-Dbar");
}
private void assertTokenizeFails(String copts, String expectedError) {
try {
tokenize(new ArrayList<String>(), copts);
fail();
} catch (ShellUtils.TokenizationException e) {
assertThat(e).hasMessage(expectedError);
}
}
@Test
public void testTokenizeEmptyString() throws Exception {
assertTokenize("");
}
@Test
public void testTokenizeFailsOnUnterminatedQuotation() {
assertTokenizeFails("-Dfoo=\"bar", "unterminated quotation");
assertTokenizeFails("-Dfoo='bar", "unterminated quotation");
assertTokenizeFails("-Dfoo=\"b'ar", "unterminated quotation");
}
private void assertTokenizeIsDualToPrettyPrint(String... args) throws Exception {
List<String> in = Arrays.asList(args);
String shellCommand = prettyPrintArgv(in);
// Assert that pretty-print is correct, i.e. dual to the actual /bin/sh
// tokenization. This test assumes no newlines in the input:
String[] execArgs = {
"/bin/sh",
"-c",
"for i in " + shellCommand + "; do echo \"$i\"; done" // tokenize, one word per line
};
String stdout = null;
try {
stdout = new String(new Command(execArgs).execute().getStdout());
} catch (Exception e) {
fail("/bin/sh failed:\n" + in + "\n" + shellCommand + "\n" + e.getMessage());
}
// We can't use stdout.split("\n") here,
// because String.split() ignores trailing empty strings.
ArrayList<String> words = Lists.newArrayList();
int index;
while ((index = stdout.indexOf('\n')) >= 0) {
words.add(stdout.substring(0, index));
stdout = stdout.substring(index + 1);
}
assertThat(words).isEqualTo(in);
// Assert that tokenize is dual to pretty-print:
List<String> out = new ArrayList<>();
try {
tokenize(out, shellCommand);
} finally {
if (out.isEmpty()) { // i.e. an exception
System.err.println(in);
}
}
assertThat(out).isEqualTo(in);
}
@Test
public void testTokenizeIsDualToPrettyPrint() throws Exception {
// tokenize() is the inverse of prettyPrintArgv(). (However, the reverse
// is not true, since there are many ways to escape the same string,
// e.g. "foo" and 'foo'.)
assertTokenizeIsDualToPrettyPrint("foo");
assertTokenizeIsDualToPrettyPrint("foo bar");
assertTokenizeIsDualToPrettyPrint("foo bar", "wiz");
assertTokenizeIsDualToPrettyPrint("'foo'");
assertTokenizeIsDualToPrettyPrint("\\'foo\\'");
assertTokenizeIsDualToPrettyPrint("${filename%.c}.o");
assertTokenizeIsDualToPrettyPrint("<html!>");
assertTokenizeIsDualToPrettyPrint("");
assertTokenizeIsDualToPrettyPrint("!@#$%^&*()");
assertTokenizeIsDualToPrettyPrint("x'y\" z");
}
}