blob: 354ed870c294d440fb1a129ff66bfb5440285d46 [file] [log] [blame]
ccalvarin58acb7b2018-03-08 10:53:24 -08001// Copyright 2018 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include <vector>
16
17#include "src/main/cpp/blaze_util.h"
18#include "src/main/cpp/blaze_util_platform.h"
19#include "src/main/cpp/option_processor.h"
20#include "src/main/cpp/util/file.h"
21#include "src/main/cpp/util/file_platform.h"
ccalvarinac69da02018-06-05 15:27:26 -070022#include "src/main/cpp/util/path.h"
ccalvarin58acb7b2018-03-08 10:53:24 -080023#include "src/main/cpp/util/strings.h"
24#include "src/main/cpp/workspace_layout.h"
ccalvarin8ceaa652018-08-24 12:44:31 -070025#include "googlemock/include/gmock/gmock.h"
ccalvarin8e9f4a82018-03-23 08:19:37 -070026#include "googletest/include/gtest/gtest.h"
ccalvarin58acb7b2018-03-08 10:53:24 -080027
28namespace blaze {
29using std::string;
30using std::unordered_map;
31using std::vector;
ccalvarin8ceaa652018-08-24 12:44:31 -070032using ::testing::MatchesRegex;
ccalvarin58acb7b2018-03-08 10:53:24 -080033
34class RcOptionsTest : public ::testing::Test {
35 protected:
36 RcOptionsTest()
arostovtsev04e182e2019-12-26 09:14:02 -080037 : test_file_dir_(blaze_util::JoinPath(
38 blaze::GetPathEnv("TEST_TMPDIR"),
39 ::testing::UnitTest::GetInstance()->current_test_info()->name())),
40 workspace_layout_() {
41 }
ccalvarin58acb7b2018-03-08 10:53:24 -080042
43 const string test_file_dir_;
44 const WorkspaceLayout workspace_layout_;
45
46 void WriteRc(const string& filename, const string& contents) {
arostovtsev04e182e2019-12-26 09:14:02 -080047 bool success_mkdir = blaze_util::MakeDirectories(test_file_dir_, 0755);
48 ASSERT_TRUE(success_mkdir) << "Failed to mkdir -p " << test_file_dir_;
49 bool success_write = blaze_util::WriteFile(
ccalvarin58acb7b2018-03-08 10:53:24 -080050 contents, blaze_util::JoinPath(test_file_dir_, filename));
arostovtsev04e182e2019-12-26 09:14:02 -080051 ASSERT_TRUE(success_write) << "Failed to write " << filename;
ccalvarin58acb7b2018-03-08 10:53:24 -080052 }
53
54 std::unique_ptr<RcFile> Parse(const string& filename,
55 RcFile::ParseError* error,
56 std::string* error_text) {
57 return RcFile::Parse(
58 blaze_util::JoinPath(test_file_dir_, filename),
59 &workspace_layout_,
60 // Set workspace to test_file_dir_ so importing %workspace%/foo works.
61 test_file_dir_,
62 error,
63 error_text);
64 }
65
66 void SuccessfullyParseRcWithExpectedArgs(
67 const string& filename,
68 const unordered_map<string, vector<string>>& expected_args_map) {
69 RcFile::ParseError error;
70 string error_text;
71 std::unique_ptr<RcFile> rc = Parse(filename, &error, &error_text);
72 EXPECT_EQ(error_text, "");
73 ASSERT_EQ(error, RcFile::ParseError::NONE);
74
75 // Test that exactly each command in the expected map was in the results,
76 // and that for each of these, exactly the expected args are found, in the
77 // correct order. Note that this is not just an exercise in rewritting map
78 // equality - the results have type RcOption, and the expected values
79 // are just strings. This is ignoring the source_path for convenience.
80 const RcFile::OptionMap& result = rc->options();
81 ASSERT_EQ(expected_args_map.size(), result.size());
82 for (const auto& command_args_pair : expected_args_map) {
83 const string& expected_command = command_args_pair.first;
84 const vector<string>& expected_args = command_args_pair.second;
85 const auto result_args_iter = result.find(expected_command);
86 ASSERT_NE(result_args_iter, rc->options().end());
87 const std::vector<RcOption>& result_args = result_args_iter->second;
88 ASSERT_EQ(result_args.size(), expected_args.size());
89 for (size_t i = 0; i < result_args.size(); ++i) {
90 EXPECT_EQ(result_args[i].option, expected_args[i]);
91 }
92 }
93 }
94};
95
ccalvarin58acb7b2018-03-08 10:53:24 -080096TEST_F(RcOptionsTest, Empty) {
97 WriteRc("empty.bazelrc",
98 "");
99 unordered_map<string, vector<string>> no_expected_args;
100 SuccessfullyParseRcWithExpectedArgs("empty.bazelrc", no_expected_args);
101}
102
103TEST_F(RcOptionsTest, Whitespace) {
104 WriteRc("whitespace.bazelrc",
105 " \n\t ");
106 unordered_map<string, vector<string>> no_expected_args;
107 SuccessfullyParseRcWithExpectedArgs("whitespace.bazelrc", no_expected_args);
108}
109
110TEST_F(RcOptionsTest, CommentedStartup) {
111 WriteRc("commented_startup.bazelrc",
112 "# startup foo");
113 unordered_map<string, vector<string>> no_expected_args;
arostovtsev04e182e2019-12-26 09:14:02 -0800114 SuccessfullyParseRcWithExpectedArgs("commented_startup.bazelrc",
115 no_expected_args);
ccalvarin58acb7b2018-03-08 10:53:24 -0800116}
117
118TEST_F(RcOptionsTest, EmptyStartupLine) {
119 WriteRc("empty_startup_line.bazelrc",
120 "startup");
121 unordered_map<string, vector<string>> no_expected_args;
122 SuccessfullyParseRcWithExpectedArgs("empty_startup_line.bazelrc",
123 no_expected_args);
124}
125
126TEST_F(RcOptionsTest, StartupWithOnlyCommentedArg) {
127 WriteRc("startup_with_comment.bazelrc",
128 "startup # bar");
129 unordered_map<string, vector<string>> no_expected_args;
130 SuccessfullyParseRcWithExpectedArgs("startup_with_comment.bazelrc",
131 no_expected_args);
132}
133
ccalvarin58acb7b2018-03-08 10:53:24 -0800134TEST_F(RcOptionsTest, SingleStartupArg) {
135 WriteRc("startup_foo.bazelrc",
136 "startup foo");
137 SuccessfullyParseRcWithExpectedArgs(
138 "startup_foo.bazelrc",
139 {{"startup", {"foo"}}});
140}
141
142TEST_F(RcOptionsTest, SingleStartupArgWithComment) {
143 WriteRc("startup_foo_and_comment.bazelrc",
144 "startup foo # comment");
145 SuccessfullyParseRcWithExpectedArgs(
146 "startup_foo_and_comment.bazelrc",
147 {{"startup", {"foo"}}});
148}
149
150TEST_F(RcOptionsTest, TwoStartupArgsOnOneLine) {
151 WriteRc("startup_foo_bar.bazelrc",
152 "startup foo bar");
153 SuccessfullyParseRcWithExpectedArgs(
154 "startup_foo_bar.bazelrc",
155 {{"startup", {"foo", "bar"}}});
156}
157
158TEST_F(RcOptionsTest, TwoStartupArgsOnOneLineTabSeparated) {
159 WriteRc("startup_with_tabs.bazelrc",
160 "startup\tfoo\tbar");
161 SuccessfullyParseRcWithExpectedArgs(
162 "startup_with_tabs.bazelrc",
163 {{"startup", {"foo", "bar"}}});
164}
165
166TEST_F(RcOptionsTest, StartupOptWithSimpleValue) {
167 WriteRc("startup_opt_with_simple_value.bazelrc",
168 "startup --opt=foo");
169 SuccessfullyParseRcWithExpectedArgs(
170 "startup_opt_with_simple_value.bazelrc",
171 {{"startup", {"--opt=foo"}}});
172}
173
174TEST_F(RcOptionsTest, StartupQuotedArg) {
175 WriteRc("startup_quoted_foo_bar.bazelrc",
176 "startup \"foo bar\"");
177 SuccessfullyParseRcWithExpectedArgs(
178 "startup_quoted_foo_bar.bazelrc",
179 {{"startup", {"foo bar"}}});
180}
181
182TEST_F(RcOptionsTest, QuotedValueStartupArgAfterEquals) {
183 WriteRc("startup_opt_quoted_arg.bazelrc",
184 "startup --opt=\"foo bar\"");
185 SuccessfullyParseRcWithExpectedArgs(
186 "startup_opt_quoted_arg.bazelrc",
187 {{"startup", {"--opt=foo bar"}}});
188}
189
190TEST_F(RcOptionsTest, QuotedValueStartupArgAfterWhitespace) {
191 WriteRc("startup_opt_quoted_arg_as_separate_token.bazelrc",
192 "startup --opt \"foo bar\"");
193 SuccessfullyParseRcWithExpectedArgs(
194 "startup_opt_quoted_arg_as_separate_token.bazelrc",
195 {{"startup", {"--opt", "foo bar"}}});
196}
197
198TEST_F(RcOptionsTest, QuotedValueStartupArgOnNewLine) {
199 WriteRc("startup_opt_quoted_arg_different_line.bazelrc",
200 "startup --opt\n"
201 "startup \"foo bar\"");
202 SuccessfullyParseRcWithExpectedArgs(
203 "startup_opt_quoted_arg_different_line.bazelrc",
204 {{"startup", {"--opt", "foo bar"}}});
205}
206
207TEST_F(RcOptionsTest, TwoOptStartup) {
208 WriteRc("startup_two_options.bazelrc",
209 "startup --opt1\n"
210 "startup --opt2");
211 SuccessfullyParseRcWithExpectedArgs(
212 "startup_two_options.bazelrc",
213 {{"startup", {"--opt1", "--opt2"}}});
214}
215
216TEST_F(RcOptionsTest, WhitespaceBeforeStartup) {
217 WriteRc("whitespace_before_command.bazelrc",
218 " startup foo\n"
219 " # indented comments\n"
220 "startup bar\n"
221 "\tstartup \t baz");
222 SuccessfullyParseRcWithExpectedArgs(
223 "whitespace_before_command.bazelrc",
224 {{"startup", {"foo", "bar", "baz"}}});
225}
226
227TEST_F(RcOptionsTest, StartupLineContinuation) {
228 WriteRc("startup_line_continuation.bazelrc",
229 "startup \\\n"
230 "foo\n"
231 "startup bar \\\n"
232 "baz");
233 SuccessfullyParseRcWithExpectedArgs(
234 "startup_line_continuation.bazelrc",
235 {{"startup", {"foo", "bar", "baz"}}});
236}
237
238TEST_F(RcOptionsTest, ManyArgStartup) {
239 WriteRc("startup_with_many_args.bazelrc",
240 "# Many arguments\n"
241 "startup foo # First argument has reasons.\n"
242 "startup --opt1 --opt2 # These arguments are split wide\n"
243 "#startup --this_is_not_an_arg\n"
244 "\n\n\n # A few empty lines for good measure\n"
245 "startup\t \"string input Value 123. \" --bar");
246 SuccessfullyParseRcWithExpectedArgs(
247 "startup_with_many_args.bazelrc",
248 {{
249 "startup",
250 {"foo", "--opt1", "--opt2", "string input Value 123. ", "--bar"}
251 }});
252}
253
ccalvarin58acb7b2018-03-08 10:53:24 -0800254TEST_F(RcOptionsTest, MultipleCommands) {
255 WriteRc("multiple_commands_intermixed.bazelrc",
256 "startup foo\n"
257 "build aaa\n"
258 "startup bar baz\n"
259 "build bbb\n"
260 "build ccc\n");
261 SuccessfullyParseRcWithExpectedArgs(
262 "multiple_commands_intermixed.bazelrc",
263 {{"startup", {"foo", "bar", "baz"}}, {"build", {"aaa", "bbb", "ccc"}}});
264}
265
ccalvarin58acb7b2018-03-08 10:53:24 -0800266TEST_F(RcOptionsTest, SimpleImportFoo) {
267 WriteRc("startup_foo.bazelrc",
268 "startup foo");
269 WriteRc("import_simple.bazelrc",
270 "import %workspace%/startup_foo.bazelrc");
271 SuccessfullyParseRcWithExpectedArgs(
272 "import_simple.bazelrc",
273 {{"startup", {"foo"}}});
274}
275
276TEST_F(RcOptionsTest, ImportFooThenAddBar) {
277 WriteRc("startup_foo.bazelrc",
278 "startup foo");
279 WriteRc("import_foo_then_bar.bazelrc",
280 "import %workspace%/startup_foo.bazelrc\n"
281 "startup bar");
282 SuccessfullyParseRcWithExpectedArgs(
283 "import_foo_then_bar.bazelrc",
284 {{"startup", {"foo", "bar"}}});
285}
286
287TEST_F(RcOptionsTest, StartupBarThenImportFoo) {
288 WriteRc("startup_foo.bazelrc",
289 "startup foo");
290 WriteRc("bar_then_import_foo.bazelrc",
291 "startup bar\n"
292 "import %workspace%/startup_foo.bazelrc");
293 SuccessfullyParseRcWithExpectedArgs(
294 "bar_then_import_foo.bazelrc",
295 {{"startup", {"bar", "foo"}}});
296}
297
ccalvarinf03acca2018-09-06 14:25:27 -0700298TEST_F(RcOptionsTest, SimpleTryImportFoo) {
299 WriteRc("startup_foo.bazelrc", "startup foo");
300 WriteRc("import_simple.bazelrc",
301 "try-import %workspace%/startup_foo.bazelrc");
302 SuccessfullyParseRcWithExpectedArgs("import_simple.bazelrc",
303 {{"startup", {"foo"}}});
304}
305
306TEST_F(RcOptionsTest, ImportTryFooThenAddBar) {
307 WriteRc("startup_foo.bazelrc", "startup foo");
308 WriteRc("import_foo_then_bar.bazelrc",
309 "try-import %workspace%/startup_foo.bazelrc\n"
310 "startup bar");
311 SuccessfullyParseRcWithExpectedArgs("import_foo_then_bar.bazelrc",
312 {{"startup", {"foo", "bar"}}});
313}
314
315TEST_F(RcOptionsTest, StartupBarThenTryImportFoo) {
316 WriteRc("startup_foo.bazelrc", "startup foo");
317 WriteRc("bar_then_import_foo.bazelrc",
318 "startup bar\n"
319 "try-import %workspace%/startup_foo.bazelrc");
320 SuccessfullyParseRcWithExpectedArgs("bar_then_import_foo.bazelrc",
321 {{"startup", {"bar", "foo"}}});
322}
323
324// Most likely, import diamonds like this are unintended, and they might lead
325// to surprising doubled values for allow_multiple options. This causes a
326// warning in option_processor, which checks for duplicates across multiple rc
327// files.
ccalvarin58acb7b2018-03-08 10:53:24 -0800328TEST_F(RcOptionsTest, ImportDiamond) {
329 WriteRc("startup_foo.bazelrc",
330 "startup foo");
331 WriteRc("import_foo_then_bar.bazelrc",
332 "import %workspace%/startup_foo.bazelrc\n"
333 "startup bar");
334 WriteRc("bar_then_import_foo.bazelrc",
335 "startup bar\n"
336 "import %workspace%/startup_foo.bazelrc");
337 WriteRc("import_diamond.bazelrc",
338 "import %workspace%/import_foo_then_bar.bazelrc\n"
339 "import %workspace%/bar_then_import_foo.bazelrc");
340 SuccessfullyParseRcWithExpectedArgs(
341 "import_diamond.bazelrc",
342 {{"startup", {"foo", "bar", "bar", "foo"}}});
343}
344
ccalvarin58acb7b2018-03-08 10:53:24 -0800345
346TEST_F(RcOptionsTest, ImportCycleFails) {
347 WriteRc("import_cycle_1.bazelrc",
348 "import %workspace%/import_cycle_2.bazelrc");
349 WriteRc("import_cycle_2.bazelrc",
350 "import %workspace%/import_cycle_1.bazelrc");
351
352 RcFile::ParseError error;
353 string error_text;
354 std::unique_ptr<RcFile> rc =
355 Parse("import_cycle_1.bazelrc", &error, &error_text);
356 EXPECT_EQ(error, RcFile::ParseError::IMPORT_LOOP);
ccalvarin8ceaa652018-08-24 12:44:31 -0700357 ASSERT_THAT(
358 error_text,
359 MatchesRegex("Import loop detected:\n"
360 " .*import_cycle_1.bazelrc\n"
361 " .*import_cycle_2.bazelrc\n"
362 " .*import_cycle_1.bazelrc\n"));
ccalvarin58acb7b2018-03-08 10:53:24 -0800363}
364
365TEST_F(RcOptionsTest, LongImportCycleFails) {
366 WriteRc("chain_to_cycle_1.bazelrc",
367 "import %workspace%/chain_to_cycle_2.bazelrc");
368 WriteRc("chain_to_cycle_2.bazelrc",
369 "import %workspace%/chain_to_cycle_3.bazelrc");
370 WriteRc("chain_to_cycle_3.bazelrc",
371 "import %workspace%/chain_to_cycle_4.bazelrc");
372 WriteRc("chain_to_cycle_4.bazelrc",
373 "import %workspace%/import_cycle_1.bazelrc");
374 WriteRc("import_cycle_1.bazelrc",
375 "import %workspace%/import_cycle_2.bazelrc");
376 WriteRc("import_cycle_2.bazelrc",
377 "import %workspace%/import_cycle_1.bazelrc");
378
379 RcFile::ParseError error;
380 string error_text;
381 std::unique_ptr<RcFile> rc =
382 Parse("chain_to_cycle_1.bazelrc", &error, &error_text);
383 EXPECT_EQ(error, RcFile::ParseError::IMPORT_LOOP);
ccalvarin8ceaa652018-08-24 12:44:31 -0700384 ASSERT_THAT(
385 error_text,
386 MatchesRegex("Import loop detected:\n"
387 " .*chain_to_cycle_1.bazelrc\n"
388 " .*chain_to_cycle_2.bazelrc\n"
389 " .*chain_to_cycle_3.bazelrc\n"
390 " .*chain_to_cycle_4.bazelrc\n"
391 " .*import_cycle_1.bazelrc\n"
392 " .*import_cycle_2.bazelrc\n"
393 " .*import_cycle_1.bazelrc\n"));
ccalvarin58acb7b2018-03-08 10:53:24 -0800394}
395
396TEST_F(RcOptionsTest, FileDoesNotExist) {
397 RcFile::ParseError error;
398 string error_text;
399 std::unique_ptr<RcFile> rc = Parse("not_a_file.bazelrc", &error, &error_text);
400 EXPECT_EQ(error, RcFile::ParseError::UNREADABLE_FILE);
ccalvarin8ceaa652018-08-24 12:44:31 -0700401 ASSERT_THAT(
402 error_text,
403 MatchesRegex(
404 "Unexpected error reading .blazerc file '.*not_a_file.bazelrc'"));
ccalvarin58acb7b2018-03-08 10:53:24 -0800405}
406
407TEST_F(RcOptionsTest, ImportedFileDoesNotExist) {
408 WriteRc("import_fake_file.bazelrc",
409 "import somefile");
410
411 RcFile::ParseError error;
412 string error_text;
413 std::unique_ptr<RcFile> rc =
414 Parse("import_fake_file.bazelrc", &error, &error_text);
415 EXPECT_EQ(error, RcFile::ParseError::UNREADABLE_FILE);
416 ASSERT_EQ(error_text, "Unexpected error reading .blazerc file 'somefile'");
417}
418
ccalvarinf03acca2018-09-06 14:25:27 -0700419TEST_F(RcOptionsTest, TryImportedFileDoesNotExist) {
420 WriteRc("try_import_fake_file.bazelrc", "try-import somefile");
421
422 unordered_map<string, vector<string>> no_expected_args;
423 SuccessfullyParseRcWithExpectedArgs("try_import_fake_file.bazelrc",
424 no_expected_args);
425}
426
ccalvarin58acb7b2018-03-08 10:53:24 -0800427TEST_F(RcOptionsTest, ImportHasTooManyArgs) {
428 WriteRc("bad_import.bazelrc",
429 "import somefile bar");
430
431 RcFile::ParseError error;
432 string error_text;
433 std::unique_ptr<RcFile> rc = Parse("bad_import.bazelrc", &error, &error_text);
434 EXPECT_EQ(error, RcFile::ParseError::INVALID_FORMAT);
ccalvarin8ceaa652018-08-24 12:44:31 -0700435 ASSERT_THAT(
436 error_text,
437 MatchesRegex("Invalid import declaration in .blazerc file "
438 "'.*bad_import.bazelrc': 'import somefile bar' \\(are you "
439 "in your source checkout/WORKSPACE\\?\\)"));
ccalvarin58acb7b2018-03-08 10:53:24 -0800440}
441
ccalvarinf03acca2018-09-06 14:25:27 -0700442TEST_F(RcOptionsTest, TryImportHasTooManyArgs) {
443 WriteRc("bad_import.bazelrc", "try-import somefile bar");
444
445 RcFile::ParseError error;
446 string error_text;
447 std::unique_ptr<RcFile> rc = Parse("bad_import.bazelrc", &error, &error_text);
448 EXPECT_EQ(error, RcFile::ParseError::INVALID_FORMAT);
449 ASSERT_THAT(
450 error_text,
451 MatchesRegex("Invalid import declaration in .blazerc file "
452 "'.*bad_import.bazelrc': 'try-import somefile bar' \\(are "
453 "you in your source checkout/WORKSPACE\\?\\)"));
454}
455
ccalvarin58acb7b2018-03-08 10:53:24 -0800456// TODO(b/34811299) The tests below identify ways that '\' used as a line
457// continuation is broken. This is on top of user-reported cases where an
458// unintentional '\' made the command on the following line show up as
459// an argument, which lead to cryptic messages. There is no value added by '\',
460// since the following line could just repeat the command, so it might be best
461// to remove this feature entirely.
462//
463// For now, these tests serve as documentation of the brokenness, and to keep
464// broken behavior consistent before we get around to fixing it.
465
466TEST_F(RcOptionsTest, BadStartupLineContinuation_HasWhitespaceAfterSlash) {
467 WriteRc("bad_startup_line_continuation.bazelrc",
468 "startup foo \\ \n"
469 "bar");
470 SuccessfullyParseRcWithExpectedArgs(
471 "bad_startup_line_continuation.bazelrc",
472 {{"startup", {"foo"}}}); // Does not contain "bar" from the next line.
473}
474
475TEST_F(RcOptionsTest, BadStartupLineContinuation_HasErroneousSlash) {
476 WriteRc("bad_startup_line_continuation.bazelrc",
477 "startup foo \\ bar");
478 SuccessfullyParseRcWithExpectedArgs(
479 "bad_startup_line_continuation.bazelrc",
480 // Whitespace between the slash and bar gets counted as part of the token.
481 {{"startup", {"foo", " bar"}}});
482}
483
484TEST_F(RcOptionsTest, BadStartupLineContinuation_HasCommentAfterSlash) {
485 WriteRc("bad_startup_line_continuation.bazelrc",
486 "startup foo \\ # comment\n"
487 "bar");
488 SuccessfullyParseRcWithExpectedArgs(
489 "bad_startup_line_continuation.bazelrc",
490 // Whitespace between the slash and comment gets counted as a new token,
491 // and the bar on the next line is ignored (it's an argumentless command).
492 {{"startup", {"foo", " "}}});
493}
494
495TEST_F(RcOptionsTest, BadStartupLineContinuation_InterpretsNextLineAsNewline) {
496 WriteRc("bad_startup_line_continuation.bazelrc",
497 "startup foo \\ #comment\n"
498 "bar baz");
499 SuccessfullyParseRcWithExpectedArgs(
500 "bad_startup_line_continuation.bazelrc",
501 // Whitespace between the slash and comment gets counted as a new token,
502 // and the bar on the next line treated as its own command, instead of as
503 // a "startup" args.
504 {{"startup", {"foo", " "}}, {"bar", {"baz"}}});
505}
506
507} // namespace blaze
508