| // 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 "googletest/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 |
| |