aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/cpp
diff options
context:
space:
mode:
authorGravatar ccalvarin <ccalvarin@google.com>2018-03-08 10:53:24 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2018-03-08 10:55:24 -0800
commit58acb7b85c6e06ac0142a8c8d53e4aa9000ccfd0 (patch)
tree226ea3dcbc56e2130df825c98a9a47fea63f5b54 /src/test/cpp
parentc7e31d3c68b86cfd1e148cebbeab3d3305a655f1 (diff)
Add unit tests for rc parsing.
These provide some testing for the following cases: - tokenization - recognizing comments - grouping of different lines by command - import ordering - import cycles - bad imports There's still room for more, in particular in the multi-command case, but this feels like a good start. Also identified some surprising behaviors that should be fixed. Leaving them tested as documentation of their broken nature. RELNOTES: None. PiperOrigin-RevId: 188355929
Diffstat (limited to 'src/test/cpp')
-rw-r--r--src/test/cpp/BUILD13
-rw-r--r--src/test/cpp/rc_options_test.cc466
2 files changed, 479 insertions, 0 deletions
diff --git a/src/test/cpp/BUILD b/src/test/cpp/BUILD
index a436556554..0e5f18cbcb 100644
--- a/src/test/cpp/BUILD
+++ b/src/test/cpp/BUILD
@@ -42,6 +42,19 @@ cc_test(
)
cc_test(
+ name = "rc_options_test",
+ size = "small",
+ srcs = ["rc_options_test.cc"],
+ deps = [
+ "//src/main/cpp:blaze_util",
+ "//src/main/cpp:option_processor",
+ "//src/main/cpp:workspace_layout",
+ "//src/main/cpp/util",
+ "//third_party:gtest",
+ ],
+)
+
+cc_test(
name = "startup_options_test",
size = "small",
srcs = ["startup_options_test.cc"],
diff --git a/src/test/cpp/rc_options_test.cc b/src/test/cpp/rc_options_test.cc
new file mode 100644
index 0000000000..46511e656e
--- /dev/null
+++ b/src/test/cpp/rc_options_test.cc
@@ -0,0 +1,466 @@
+// Copyright 2018 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.
+
+#include <vector>
+
+#include "src/main/cpp/blaze_util.h"
+#include "src/main/cpp/blaze_util_platform.h"
+#include "src/main/cpp/option_processor.h"
+#include "src/main/cpp/util/file.h"
+#include "src/main/cpp/util/file_platform.h"
+#include "src/main/cpp/util/strings.h"
+#include "src/main/cpp/workspace_layout.h"
+#include "gtest/gtest.h"
+
+namespace blaze {
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+class RcOptionsTest : public ::testing::Test {
+ protected:
+ RcOptionsTest()
+ : test_file_dir_(blaze::GetEnv("TEST_TMPDIR")),
+ workspace_layout_() {}
+
+ const string test_file_dir_;
+ const WorkspaceLayout workspace_layout_;
+
+ void WriteRc(const string& filename, const string& contents) {
+ bool success = blaze_util::WriteFile(
+ contents, blaze_util::JoinPath(test_file_dir_, filename));
+ ASSERT_TRUE(success) << "Failed to write " << filename;
+ }
+
+ std::unique_ptr<RcFile> Parse(const string& filename,
+ RcFile::ParseError* error,
+ std::string* error_text) {
+ return RcFile::Parse(
+ blaze_util::JoinPath(test_file_dir_, filename),
+ &workspace_layout_,
+ // Set workspace to test_file_dir_ so importing %workspace%/foo works.
+ test_file_dir_,
+ error,
+ error_text);
+ }
+
+ void SuccessfullyParseRcWithExpectedArgs(
+ const string& filename,
+ const unordered_map<string, vector<string>>& expected_args_map) {
+ RcFile::ParseError error;
+ string error_text;
+ std::unique_ptr<RcFile> rc = Parse(filename, &error, &error_text);
+ EXPECT_EQ(error_text, "");
+ ASSERT_EQ(error, RcFile::ParseError::NONE);
+
+ // Test that exactly each command in the expected map was in the results,
+ // and that for each of these, exactly the expected args are found, in the
+ // correct order. Note that this is not just an exercise in rewritting map
+ // equality - the results have type RcOption, and the expected values
+ // are just strings. This is ignoring the source_path for convenience.
+ const RcFile::OptionMap& result = rc->options();
+ ASSERT_EQ(expected_args_map.size(), result.size());
+ for (const auto& command_args_pair : expected_args_map) {
+ const string& expected_command = command_args_pair.first;
+ const vector<string>& expected_args = command_args_pair.second;
+ const auto result_args_iter = result.find(expected_command);
+ ASSERT_NE(result_args_iter, rc->options().end());
+ const std::vector<RcOption>& result_args = result_args_iter->second;
+ ASSERT_EQ(result_args.size(), expected_args.size());
+ for (size_t i = 0; i < result_args.size(); ++i) {
+ EXPECT_EQ(result_args[i].option, expected_args[i]);
+ }
+ }
+ }
+};
+
+// Effectively empty file tests
+
+TEST_F(RcOptionsTest, Empty) {
+ WriteRc("empty.bazelrc",
+ "");
+ unordered_map<string, vector<string>> no_expected_args;
+ SuccessfullyParseRcWithExpectedArgs("empty.bazelrc", no_expected_args);
+}
+
+TEST_F(RcOptionsTest, Whitespace) {
+ WriteRc("whitespace.bazelrc",
+ " \n\t ");
+ unordered_map<string, vector<string>> no_expected_args;
+ SuccessfullyParseRcWithExpectedArgs("whitespace.bazelrc", no_expected_args);
+}
+
+TEST_F(RcOptionsTest, CommentedStartup) {
+ WriteRc("commented_startup.bazelrc",
+ "# startup foo");
+ unordered_map<string, vector<string>> no_expected_args;
+ SuccessfullyParseRcWithExpectedArgs("whitespace.bazelrc", no_expected_args);
+}
+
+TEST_F(RcOptionsTest, EmptyStartupLine) {
+ WriteRc("empty_startup_line.bazelrc",
+ "startup");
+ unordered_map<string, vector<string>> no_expected_args;
+ SuccessfullyParseRcWithExpectedArgs("empty_startup_line.bazelrc",
+ no_expected_args);
+}
+
+TEST_F(RcOptionsTest, StartupWithOnlyCommentedArg) {
+ WriteRc("startup_with_comment.bazelrc",
+ "startup # bar");
+ unordered_map<string, vector<string>> no_expected_args;
+ SuccessfullyParseRcWithExpectedArgs("startup_with_comment.bazelrc",
+ no_expected_args);
+}
+
+// Single command tests - testing tokenization and accumulation of arguments.
+
+TEST_F(RcOptionsTest, SingleStartupArg) {
+ WriteRc("startup_foo.bazelrc",
+ "startup foo");
+ SuccessfullyParseRcWithExpectedArgs(
+ "startup_foo.bazelrc",
+ {{"startup", {"foo"}}});
+}
+
+TEST_F(RcOptionsTest, SingleStartupArgWithComment) {
+ WriteRc("startup_foo_and_comment.bazelrc",
+ "startup foo # comment");
+ SuccessfullyParseRcWithExpectedArgs(
+ "startup_foo_and_comment.bazelrc",
+ {{"startup", {"foo"}}});
+}
+
+TEST_F(RcOptionsTest, TwoStartupArgsOnOneLine) {
+ WriteRc("startup_foo_bar.bazelrc",
+ "startup foo bar");
+ SuccessfullyParseRcWithExpectedArgs(
+ "startup_foo_bar.bazelrc",
+ {{"startup", {"foo", "bar"}}});
+}
+
+TEST_F(RcOptionsTest, TwoStartupArgsOnOneLineTabSeparated) {
+ WriteRc("startup_with_tabs.bazelrc",
+ "startup\tfoo\tbar");
+ SuccessfullyParseRcWithExpectedArgs(
+ "startup_with_tabs.bazelrc",
+ {{"startup", {"foo", "bar"}}});
+}
+
+TEST_F(RcOptionsTest, StartupOptWithSimpleValue) {
+ WriteRc("startup_opt_with_simple_value.bazelrc",
+ "startup --opt=foo");
+ SuccessfullyParseRcWithExpectedArgs(
+ "startup_opt_with_simple_value.bazelrc",
+ {{"startup", {"--opt=foo"}}});
+}
+
+TEST_F(RcOptionsTest, StartupQuotedArg) {
+ WriteRc("startup_quoted_foo_bar.bazelrc",
+ "startup \"foo bar\"");
+ SuccessfullyParseRcWithExpectedArgs(
+ "startup_quoted_foo_bar.bazelrc",
+ {{"startup", {"foo bar"}}});
+}
+
+TEST_F(RcOptionsTest, QuotedValueStartupArgAfterEquals) {
+ WriteRc("startup_opt_quoted_arg.bazelrc",
+ "startup --opt=\"foo bar\"");
+ SuccessfullyParseRcWithExpectedArgs(
+ "startup_opt_quoted_arg.bazelrc",
+ {{"startup", {"--opt=foo bar"}}});
+}
+
+TEST_F(RcOptionsTest, QuotedValueStartupArgAfterWhitespace) {
+ WriteRc("startup_opt_quoted_arg_as_separate_token.bazelrc",
+ "startup --opt \"foo bar\"");
+ SuccessfullyParseRcWithExpectedArgs(
+ "startup_opt_quoted_arg_as_separate_token.bazelrc",
+ {{"startup", {"--opt", "foo bar"}}});
+}
+
+TEST_F(RcOptionsTest, QuotedValueStartupArgOnNewLine) {
+ WriteRc("startup_opt_quoted_arg_different_line.bazelrc",
+ "startup --opt\n"
+ "startup \"foo bar\"");
+ SuccessfullyParseRcWithExpectedArgs(
+ "startup_opt_quoted_arg_different_line.bazelrc",
+ {{"startup", {"--opt", "foo bar"}}});
+}
+
+TEST_F(RcOptionsTest, TwoOptStartup) {
+ WriteRc("startup_two_options.bazelrc",
+ "startup --opt1\n"
+ "startup --opt2");
+ SuccessfullyParseRcWithExpectedArgs(
+ "startup_two_options.bazelrc",
+ {{"startup", {"--opt1", "--opt2"}}});
+}
+
+TEST_F(RcOptionsTest, WhitespaceBeforeStartup) {
+ WriteRc("whitespace_before_command.bazelrc",
+ " startup foo\n"
+ " # indented comments\n"
+ "startup bar\n"
+ "\tstartup \t baz");
+ SuccessfullyParseRcWithExpectedArgs(
+ "whitespace_before_command.bazelrc",
+ {{"startup", {"foo", "bar", "baz"}}});
+}
+
+TEST_F(RcOptionsTest, StartupLineContinuation) {
+ WriteRc("startup_line_continuation.bazelrc",
+ "startup \\\n"
+ "foo\n"
+ "startup bar \\\n"
+ "baz");
+ SuccessfullyParseRcWithExpectedArgs(
+ "startup_line_continuation.bazelrc",
+ {{"startup", {"foo", "bar", "baz"}}});
+}
+
+TEST_F(RcOptionsTest, ManyArgStartup) {
+ WriteRc("startup_with_many_args.bazelrc",
+ "# Many arguments\n"
+ "startup foo # First argument has reasons.\n"
+ "startup --opt1 --opt2 # These arguments are split wide\n"
+ "#startup --this_is_not_an_arg\n"
+ "\n\n\n # A few empty lines for good measure\n"
+ "startup\t \"string input Value 123. \" --bar");
+ SuccessfullyParseRcWithExpectedArgs(
+ "startup_with_many_args.bazelrc",
+ {{
+ "startup",
+ {"foo", "--opt1", "--opt2", "string input Value 123. ", "--bar"}
+ }});
+}
+
+// Testing which commands different args belong to.
+
+TEST_F(RcOptionsTest, MultipleCommands) {
+ WriteRc("multiple_commands_intermixed.bazelrc",
+ "startup foo\n"
+ "build aaa\n"
+ "startup bar baz\n"
+ "build bbb\n"
+ "build ccc\n");
+ SuccessfullyParseRcWithExpectedArgs(
+ "multiple_commands_intermixed.bazelrc",
+ {{"startup", {"foo", "bar", "baz"}}, {"build", {"aaa", "bbb", "ccc"}}});
+}
+
+// Successful import tests
+
+TEST_F(RcOptionsTest, SimpleImportFoo) {
+ WriteRc("startup_foo.bazelrc",
+ "startup foo");
+ WriteRc("import_simple.bazelrc",
+ "import %workspace%/startup_foo.bazelrc");
+ SuccessfullyParseRcWithExpectedArgs(
+ "import_simple.bazelrc",
+ {{"startup", {"foo"}}});
+}
+
+TEST_F(RcOptionsTest, ImportFooThenAddBar) {
+ WriteRc("startup_foo.bazelrc",
+ "startup foo");
+ WriteRc("import_foo_then_bar.bazelrc",
+ "import %workspace%/startup_foo.bazelrc\n"
+ "startup bar");
+ SuccessfullyParseRcWithExpectedArgs(
+ "import_foo_then_bar.bazelrc",
+ {{"startup", {"foo", "bar"}}});
+}
+
+TEST_F(RcOptionsTest, StartupBarThenImportFoo) {
+ WriteRc("startup_foo.bazelrc",
+ "startup foo");
+ WriteRc("bar_then_import_foo.bazelrc",
+ "startup bar\n"
+ "import %workspace%/startup_foo.bazelrc");
+ SuccessfullyParseRcWithExpectedArgs(
+ "bar_then_import_foo.bazelrc",
+ {{"startup", {"bar", "foo"}}});
+}
+
+// Consider making this an error, or at least a warning - most likely, import
+// diamonds like this are unintended, and they might lead to surprising doubled
+// values for allow_multiple options.
+TEST_F(RcOptionsTest, ImportDiamond) {
+ WriteRc("startup_foo.bazelrc",
+ "startup foo");
+ WriteRc("import_foo_then_bar.bazelrc",
+ "import %workspace%/startup_foo.bazelrc\n"
+ "startup bar");
+ WriteRc("bar_then_import_foo.bazelrc",
+ "startup bar\n"
+ "import %workspace%/startup_foo.bazelrc");
+ WriteRc("import_diamond.bazelrc",
+ "import %workspace%/import_foo_then_bar.bazelrc\n"
+ "import %workspace%/bar_then_import_foo.bazelrc");
+ SuccessfullyParseRcWithExpectedArgs(
+ "import_diamond.bazelrc",
+ {{"startup", {"foo", "bar", "bar", "foo"}}});
+}
+
+// Testing failure modes
+
+TEST_F(RcOptionsTest, ImportCycleFails) {
+ WriteRc("import_cycle_1.bazelrc",
+ "import %workspace%/import_cycle_2.bazelrc");
+ WriteRc("import_cycle_2.bazelrc",
+ "import %workspace%/import_cycle_1.bazelrc");
+
+ RcFile::ParseError error;
+ string error_text;
+ std::unique_ptr<RcFile> rc =
+ Parse("import_cycle_1.bazelrc", &error, &error_text);
+ EXPECT_EQ(error, RcFile::ParseError::IMPORT_LOOP);
+ string expected_error;
+ blaze_util::StringPrintf(
+ &expected_error, "Import loop detected:\n %s\n %s\n %s\n",
+ blaze_util::JoinPath(test_file_dir_, "import_cycle_1.bazelrc").c_str(),
+ blaze_util::JoinPath(test_file_dir_, "import_cycle_2.bazelrc").c_str(),
+ blaze_util::JoinPath(test_file_dir_, "import_cycle_1.bazelrc").c_str());
+ ASSERT_EQ(error_text, expected_error);
+}
+
+TEST_F(RcOptionsTest, LongImportCycleFails) {
+ WriteRc("chain_to_cycle_1.bazelrc",
+ "import %workspace%/chain_to_cycle_2.bazelrc");
+ WriteRc("chain_to_cycle_2.bazelrc",
+ "import %workspace%/chain_to_cycle_3.bazelrc");
+ WriteRc("chain_to_cycle_3.bazelrc",
+ "import %workspace%/chain_to_cycle_4.bazelrc");
+ WriteRc("chain_to_cycle_4.bazelrc",
+ "import %workspace%/import_cycle_1.bazelrc");
+ WriteRc("import_cycle_1.bazelrc",
+ "import %workspace%/import_cycle_2.bazelrc");
+ WriteRc("import_cycle_2.bazelrc",
+ "import %workspace%/import_cycle_1.bazelrc");
+
+ RcFile::ParseError error;
+ string error_text;
+ std::unique_ptr<RcFile> rc =
+ Parse("chain_to_cycle_1.bazelrc", &error, &error_text);
+ EXPECT_EQ(error, RcFile::ParseError::IMPORT_LOOP);
+ string expected_error;
+ blaze_util::StringPrintf(
+ &expected_error,
+ "Import loop detected:\n %s\n %s\n %s\n %s\n %s\n %s\n %s\n",
+ blaze_util::JoinPath(test_file_dir_, "chain_to_cycle_1.bazelrc").c_str(),
+ blaze_util::JoinPath(test_file_dir_, "chain_to_cycle_2.bazelrc").c_str(),
+ blaze_util::JoinPath(test_file_dir_, "chain_to_cycle_3.bazelrc").c_str(),
+ blaze_util::JoinPath(test_file_dir_, "chain_to_cycle_4.bazelrc").c_str(),
+ blaze_util::JoinPath(test_file_dir_, "import_cycle_1.bazelrc").c_str(),
+ blaze_util::JoinPath(test_file_dir_, "import_cycle_2.bazelrc").c_str(),
+ blaze_util::JoinPath(test_file_dir_, "import_cycle_1.bazelrc").c_str());
+ ASSERT_EQ(error_text, expected_error);
+}
+
+TEST_F(RcOptionsTest, FileDoesNotExist) {
+ RcFile::ParseError error;
+ string error_text;
+ std::unique_ptr<RcFile> rc = Parse("not_a_file.bazelrc", &error, &error_text);
+ EXPECT_EQ(error, RcFile::ParseError::UNREADABLE_FILE);
+ string expected_error;
+ blaze_util::StringPrintf(
+ &expected_error, "Unexpected error reading .blazerc file '%s'",
+ blaze_util::JoinPath(test_file_dir_, "not_a_file.bazelrc").c_str());
+ ASSERT_EQ(error_text, expected_error);
+}
+
+TEST_F(RcOptionsTest, ImportedFileDoesNotExist) {
+ WriteRc("import_fake_file.bazelrc",
+ "import somefile");
+
+ RcFile::ParseError error;
+ string error_text;
+ std::unique_ptr<RcFile> rc =
+ Parse("import_fake_file.bazelrc", &error, &error_text);
+ EXPECT_EQ(error, RcFile::ParseError::UNREADABLE_FILE);
+ ASSERT_EQ(error_text, "Unexpected error reading .blazerc file 'somefile'");
+}
+
+TEST_F(RcOptionsTest, ImportHasTooManyArgs) {
+ WriteRc("bad_import.bazelrc",
+ "import somefile bar");
+
+ RcFile::ParseError error;
+ string error_text;
+ std::unique_ptr<RcFile> rc = Parse("bad_import.bazelrc", &error, &error_text);
+ EXPECT_EQ(error, RcFile::ParseError::INVALID_FORMAT);
+
+ string expected_error;
+ blaze_util::StringPrintf(
+ &expected_error,
+ "Invalid import declaration in .blazerc file '%s': "
+ "'import somefile bar' (are you in your source checkout/WORKSPACE?)",
+ blaze_util::JoinPath(test_file_dir_, "bad_import.bazelrc").c_str());
+ ASSERT_EQ(error_text, expected_error);
+}
+
+// TODO(b/34811299) The tests below identify ways that '\' used as a line
+// continuation is broken. This is on top of user-reported cases where an
+// unintentional '\' made the command on the following line show up as
+// an argument, which lead to cryptic messages. There is no value added by '\',
+// since the following line could just repeat the command, so it might be best
+// to remove this feature entirely.
+//
+// For now, these tests serve as documentation of the brokenness, and to keep
+// broken behavior consistent before we get around to fixing it.
+
+TEST_F(RcOptionsTest, BadStartupLineContinuation_HasWhitespaceAfterSlash) {
+ WriteRc("bad_startup_line_continuation.bazelrc",
+ "startup foo \\ \n"
+ "bar");
+ SuccessfullyParseRcWithExpectedArgs(
+ "bad_startup_line_continuation.bazelrc",
+ {{"startup", {"foo"}}}); // Does not contain "bar" from the next line.
+}
+
+TEST_F(RcOptionsTest, BadStartupLineContinuation_HasErroneousSlash) {
+ WriteRc("bad_startup_line_continuation.bazelrc",
+ "startup foo \\ bar");
+ SuccessfullyParseRcWithExpectedArgs(
+ "bad_startup_line_continuation.bazelrc",
+ // Whitespace between the slash and bar gets counted as part of the token.
+ {{"startup", {"foo", " bar"}}});
+}
+
+TEST_F(RcOptionsTest, BadStartupLineContinuation_HasCommentAfterSlash) {
+ WriteRc("bad_startup_line_continuation.bazelrc",
+ "startup foo \\ # comment\n"
+ "bar");
+ SuccessfullyParseRcWithExpectedArgs(
+ "bad_startup_line_continuation.bazelrc",
+ // Whitespace between the slash and comment gets counted as a new token,
+ // and the bar on the next line is ignored (it's an argumentless command).
+ {{"startup", {"foo", " "}}});
+}
+
+TEST_F(RcOptionsTest, BadStartupLineContinuation_InterpretsNextLineAsNewline) {
+ WriteRc("bad_startup_line_continuation.bazelrc",
+ "startup foo \\ #comment\n"
+ "bar baz");
+ SuccessfullyParseRcWithExpectedArgs(
+ "bad_startup_line_continuation.bazelrc",
+ // Whitespace between the slash and comment gets counted as a new token,
+ // and the bar on the next line treated as its own command, instead of as
+ // a "startup" args.
+ {{"startup", {"foo", " "}}, {"bar", {"baz"}}});
+}
+
+} // namespace blaze
+