diff options
author | ccalvarin <ccalvarin@google.com> | 2018-03-08 10:53:24 -0800 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2018-03-08 10:55:24 -0800 |
commit | 58acb7b85c6e06ac0142a8c8d53e4aa9000ccfd0 (patch) | |
tree | 226ea3dcbc56e2130df825c98a9a47fea63f5b54 /src/test/cpp | |
parent | c7e31d3c68b86cfd1e148cebbeab3d3305a655f1 (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/BUILD | 13 | ||||
-rw-r--r-- | src/test/cpp/rc_options_test.cc | 466 |
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 + |