1 /*
2  * Copyright (C) 2015, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "options.h"
18 
19 #include <iostream>
20 #include <memory>
21 #include <string>
22 #include <vector>
23 
24 #include <gmock/gmock.h>
25 #include <gtest/gtest.h>
26 
27 #include "diagnostics.h"
28 
29 using android::aidl::DiagnosticID;
30 using android::aidl::DiagnosticSeverity;
31 using std::cerr;
32 using std::endl;
33 using std::string;
34 using std::unique_ptr;
35 using std::vector;
36 using testing::internal::CaptureStderr;
37 using testing::internal::GetCapturedStderr;
38 
39 namespace android {
40 namespace aidl {
41 namespace {
42 
43 const char kPreprocessCommandOutputFile[] = "output_file_name";
44 const char kPreprocessCommandInput1[] = "input1.aidl";
45 const char kPreprocessCommandInput2[] = "input2.aidl";
46 const char kPreprocessCommandInput3[] = "input3.aidl";
47 const char* kPreprocessCommand[] = {
48     "aidl", "--preprocess",
49     kPreprocessCommandOutputFile,
50     kPreprocessCommandInput1,
51     kPreprocessCommandInput2,
52     kPreprocessCommandInput3,
53     nullptr,
54 };
55 
56 const char kCompileCommandInput[] = "directory/ITool.aidl";
57 const char kCompileCommandIncludePath[] = "-Iinclude_path";
58 const char* kCompileJavaCommand[] = {
59     "aidl",
60     "-b",
61     kCompileCommandIncludePath,
62     kCompileCommandInput,
63     nullptr,
64 };
65 const char kCompileCommandJavaOutput[] = "directory/ITool.java";
66 
67 const char kCompileDepFileNinja[] = "--ninja";
68 const char* kCompileJavaCommandNinja[] = {
69     "aidl",
70     "-b",
71     kCompileDepFileNinja,
72     kCompileCommandIncludePath,
73     kCompileCommandInput,
74     nullptr,
75 };
76 
77 const char kCompileDepFile[] = "-doutput.deps";
78 const char kCompileCommandHeaderDir[] = "output/dir/";
79 const char kCompileCommandCppOutput[] = "some/file.cpp";
80 const char* kCompileCppCommand[] = {
81     "aidl-cpp",
82     kCompileCommandIncludePath,
83     kCompileDepFile,
84     kCompileCommandInput,
85     kCompileCommandHeaderDir,
86     kCompileCommandCppOutput,
87     nullptr,
88 };
89 const char* kCompileCppCommandNinja[] = {
90     "aidl-cpp",
91     kCompileCommandIncludePath,
92     kCompileDepFile,
93     kCompileDepFileNinja,
94     kCompileCommandInput,
95     kCompileCommandHeaderDir,
96     kCompileCommandCppOutput,
97     nullptr,
98 };
99 
GetOptions(const char * command[],Options::Language default_lang=Options::Language::JAVA)100 unique_ptr<Options> GetOptions(const char* command[],
101                                Options::Language default_lang = Options::Language::JAVA) {
102   int argc = 0;
103   const char** command_part = command;
104   for (; *command_part; ++argc, ++command_part) {}
105   unique_ptr<Options> ret(new Options(argc, command, default_lang));
106   if (!ret->Ok()) {
107     cerr << ret->GetErrorMessage();
108     cerr << "Failed to parse command line:";
109     for (int i = 0; i < argc; ++i) {
110       cerr << " " << command[i];
111       cerr << endl;
112     }
113   }
114   EXPECT_NE(ret, nullptr) << "Failed to parse options!";
115   return ret;
116 }
117 
118 }  // namespace
119 
TEST(OptionsTests,ParsesPreprocess)120 TEST(OptionsTests, ParsesPreprocess) {
121   unique_ptr<Options> options = GetOptions(kPreprocessCommand);
122   EXPECT_EQ(Options::Task::PREPROCESS, options->GetTask());
123   EXPECT_EQ(false, options->FailOnParcelable());
124   EXPECT_EQ(0u, options->ImportDirs().size());
125   EXPECT_EQ(0u, options->PreprocessedFiles().size());
126   EXPECT_EQ(string{kPreprocessCommandOutputFile}, options->OutputFile());
127   EXPECT_EQ(false, options->AutoDepFile());
128   const vector<string> expected_input{kPreprocessCommandInput1,
129                                       kPreprocessCommandInput2,
130                                       kPreprocessCommandInput3};
131   EXPECT_EQ(expected_input, options->InputFiles());
132 }
133 
TEST(OptionsTests,ParsesCompileJava)134 TEST(OptionsTests, ParsesCompileJava) {
135   unique_ptr<Options> options = GetOptions(kCompileJavaCommand);
136   EXPECT_EQ(Options::Task::COMPILE, options->GetTask());
137   EXPECT_EQ(Options::Language::JAVA, options->TargetLanguage());
138   EXPECT_EQ(true, options->FailOnParcelable());
139   EXPECT_EQ(1u, options->ImportDirs().size());
140   EXPECT_EQ(0u, options->PreprocessedFiles().size());
141   EXPECT_EQ(string{kCompileCommandInput}, options->InputFiles().front());
142   EXPECT_EQ(string{kCompileCommandJavaOutput}, options->OutputFile());
143   EXPECT_EQ(false, options->AutoDepFile());
144   EXPECT_EQ(false, options->DependencyFileNinja());
145 }
146 
TEST(OptionsTests,ParsesCompileJavaNinja)147 TEST(OptionsTests, ParsesCompileJavaNinja) {
148   unique_ptr<Options> options = GetOptions(kCompileJavaCommandNinja);
149   EXPECT_EQ(Options::Task::COMPILE, options->GetTask());
150   EXPECT_EQ(Options::Language::JAVA, options->TargetLanguage());
151   EXPECT_EQ(true, options->FailOnParcelable());
152   EXPECT_EQ(1u, options->ImportDirs().size());
153   EXPECT_EQ(0u, options->PreprocessedFiles().size());
154   EXPECT_EQ(string{kCompileCommandInput}, options->InputFiles().front());
155   EXPECT_EQ(string{kCompileCommandJavaOutput}, options->OutputFile());
156   EXPECT_EQ(false, options->AutoDepFile());
157   EXPECT_EQ(true, options->DependencyFileNinja());
158 }
159 
TEST(OptionsTests,ParsesCompileCpp)160 TEST(OptionsTests, ParsesCompileCpp) {
161   unique_ptr<Options> options = GetOptions(kCompileCppCommand, Options::Language::CPP);
162   ASSERT_EQ(1u, options->ImportDirs().size());
163   EXPECT_EQ(string{kCompileCommandIncludePath}.substr(2), *options->ImportDirs().begin());
164   EXPECT_EQ(string{kCompileDepFile}.substr(2), options->DependencyFile());
165   EXPECT_EQ(false, options->DependencyFileNinja());
166   EXPECT_EQ(kCompileCommandInput, options->InputFiles().front());
167   EXPECT_EQ(kCompileCommandHeaderDir, options->OutputHeaderDir());
168   EXPECT_EQ(kCompileCommandCppOutput, options->OutputFile());
169 }
170 
TEST(OptionsTests,ParsesCompileCppNinja)171 TEST(OptionsTests, ParsesCompileCppNinja) {
172   unique_ptr<Options> options = GetOptions(kCompileCppCommandNinja, Options::Language::CPP);
173   ASSERT_EQ(1u, options->ImportDirs().size());
174   EXPECT_EQ(string{kCompileCommandIncludePath}.substr(2), *options->ImportDirs().begin());
175   EXPECT_EQ(string{kCompileDepFile}.substr(2), options->DependencyFile());
176   EXPECT_EQ(true, options->DependencyFileNinja());
177   EXPECT_EQ(kCompileCommandInput, options->InputFiles().front());
178   EXPECT_EQ(kCompileCommandHeaderDir, options->OutputHeaderDir());
179   EXPECT_EQ(kCompileCommandCppOutput, options->OutputFile());
180 }
181 
TEST(OptionsTests,ParsesCompileJavaMultiInput)182 TEST(OptionsTests, ParsesCompileJavaMultiInput) {
183   const char* argv[] = {
184       "aidl",
185       "--lang=java",
186       kCompileCommandIncludePath,
187       "-o src_out",
188       "directory/input1.aidl",
189       "directory/input2.aidl",
190       "directory/input3.aidl",
191       nullptr,
192   };
193   unique_ptr<Options> options = GetOptions(argv);
194   EXPECT_EQ(Options::Task::COMPILE, options->GetTask());
195   EXPECT_EQ(Options::Language::JAVA, options->TargetLanguage());
196   EXPECT_EQ(false, options->FailOnParcelable());
197   EXPECT_EQ(1u, options->ImportDirs().size());
198   EXPECT_EQ(0u, options->PreprocessedFiles().size());
199   const vector<string> expected_input{"directory/input1.aidl", "directory/input2.aidl",
200                                       "directory/input3.aidl"};
201   EXPECT_EQ(expected_input, options->InputFiles());
202   EXPECT_EQ(string{""}, options->OutputFile());
203   EXPECT_EQ(false, options->AutoDepFile());
204   EXPECT_EQ(false, options->DependencyFileNinja());
205   EXPECT_EQ(string{""}, options->OutputHeaderDir());
206   EXPECT_EQ(string{"src_out/"}, options->OutputDir());
207 }
208 
TEST(OptionsTests,ParsesCompileRust)209 TEST(OptionsTests, ParsesCompileRust) {
210   const char* argv[] = {
211       "aidl",       "--lang=rust",        kCompileCommandIncludePath,
212       "-o src_out", kCompileCommandInput, nullptr,
213   };
214   unique_ptr<Options> options = GetOptions(argv);
215   EXPECT_EQ(Options::Task::COMPILE, options->GetTask());
216   EXPECT_EQ(Options::Language::RUST, options->TargetLanguage());
217   EXPECT_EQ(false, options->FailOnParcelable());
218   EXPECT_EQ(1u, options->ImportDirs().size());
219   EXPECT_EQ(0u, options->PreprocessedFiles().size());
220   EXPECT_EQ(string{kCompileCommandInput}, options->InputFiles().front());
221   EXPECT_EQ(string{""}, options->OutputFile());
222   EXPECT_EQ(string{""}, options->OutputHeaderDir());
223   EXPECT_EQ(string{"src_out/"}, options->OutputDir());
224   EXPECT_EQ(false, options->AutoDepFile());
225   EXPECT_EQ(false, options->DependencyFileNinja());
226 }
227 
TEST(OptionsTests,ParsesCompileJavaInvalid_OutRequired)228 TEST(OptionsTests, ParsesCompileJavaInvalid_OutRequired) {
229   // -o option is required
230   string expected_error = "Output directory is not set. Set with --out.";
231   CaptureStderr();
232   const char* arg_with_no_out_dir[] = {
233       "aidl",
234       "--lang=java",
235       kCompileCommandIncludePath,
236       "directory/input1.aidl",
237       "directory/input2.aidl",
238       "directory/input3.aidl",
239       nullptr,
240   };
241   EXPECT_EQ(false, GetOptions(arg_with_no_out_dir)->Ok());
242   EXPECT_THAT(GetCapturedStderr(), testing::HasSubstr(expected_error));
243 }
244 
TEST(OptionsTests,ParsesCompileJavaInvalid_RejectHeaderOut)245 TEST(OptionsTests, ParsesCompileJavaInvalid_RejectHeaderOut) {
246   string expected_error = "Header output directory is set, which does not make sense for Java.";
247   CaptureStderr();
248   // -h options is not for Java
249   const char* arg_with_header_dir[] = {
250       "aidl",          "--lang=java",           kCompileCommandIncludePath, "-o src_out",
251       "-h header_out", "directory/input1.aidl", "directory/input2.aidl",    "directory/input3.aidl",
252       nullptr,
253   };
254   EXPECT_EQ(false, GetOptions(arg_with_header_dir)->Ok());
255   EXPECT_THAT(GetCapturedStderr(), testing::HasSubstr(expected_error));
256 }
257 
TEST(OptionsTests,ParsesCompileCppMultiInput)258 TEST(OptionsTests, ParsesCompileCppMultiInput) {
259   const char* argv[] = {
260       "aidl",
261       "--lang=cpp",
262       kCompileCommandIncludePath,
263       "-h header_out",
264       "-o src_out",
265       "directory/input1.aidl",
266       "directory/input2.aidl",
267       "directory/input3.aidl",
268       nullptr,
269   };
270   unique_ptr<Options> options = GetOptions(argv);
271   EXPECT_EQ(Options::Task::COMPILE, options->GetTask());
272   EXPECT_EQ(Options::Language::CPP, options->TargetLanguage());
273   EXPECT_EQ(false, options->FailOnParcelable());
274   EXPECT_EQ(1u, options->ImportDirs().size());
275   EXPECT_EQ(0u, options->PreprocessedFiles().size());
276   const vector<string> expected_input{"directory/input1.aidl", "directory/input2.aidl",
277                                       "directory/input3.aidl"};
278   EXPECT_EQ(expected_input, options->InputFiles());
279   EXPECT_EQ(string{""}, options->OutputFile());
280   EXPECT_EQ(false, options->AutoDepFile());
281   EXPECT_EQ(false, options->DependencyFileNinja());
282   EXPECT_EQ(string{"header_out/"}, options->OutputHeaderDir());
283   EXPECT_EQ(string{"src_out/"}, options->OutputDir());
284 }
285 
TEST(OptionsTests,ParsesCompileCppInvalid_OutRequired)286 TEST(OptionsTests, ParsesCompileCppInvalid_OutRequired) {
287   // -o option is required
288   string expected_error = "Output directory is not set. Set with --out.";
289   CaptureStderr();
290   const char* arg_with_no_out_dir[] = {
291       "aidl",
292       "--lang=cpp",
293       kCompileCommandIncludePath,
294       "directory/input1.aidl",
295       "directory/input2.aidl",
296       "directory/input3.aidl",
297       nullptr,
298   };
299   EXPECT_EQ(false, GetOptions(arg_with_no_out_dir)->Ok());
300   EXPECT_THAT(GetCapturedStderr(), testing::HasSubstr(expected_error));
301 }
302 
TEST(OptionsTests,ParsesCompileCppInvalid_HeaderOutRequired)303 TEST(OptionsTests, ParsesCompileCppInvalid_HeaderOutRequired) {
304   // -h options is required as well
305   string expected_error = "Header output directory is not set. Set with --header_out";
306   CaptureStderr();
307   const char* arg_with_no_header_dir[] = {
308       "aidl",
309       "--lang=cpp",
310       kCompileCommandIncludePath,
311       "-o src_out",
312       "directory/input1.aidl",
313       "directory/input2.aidl",
314       "directory/input3.aidl",
315       nullptr,
316   };
317   EXPECT_EQ(false, GetOptions(arg_with_no_header_dir)->Ok());
318   EXPECT_THAT(GetCapturedStderr(), testing::HasSubstr(expected_error));
319 }
320 
TEST(OptionsTests,ParsesCompileRustInvalid_OutRequired)321 TEST(OptionsTests, ParsesCompileRustInvalid_OutRequired) {
322   // -o option is required
323   string expected_error = "Output directory is not set. Set with --out";
324   CaptureStderr();
325   const char* arg_with_no_out_dir[] = {
326       "aidl",
327       "--lang=rust",
328       kCompileCommandIncludePath,
329       "directory/input1.aidl",
330       "directory/input2.aidl",
331       "directory/input3.aidl",
332       nullptr,
333   };
334   EXPECT_EQ(false, GetOptions(arg_with_no_out_dir)->Ok());
335   EXPECT_THAT(GetCapturedStderr(), testing::HasSubstr(expected_error));
336 }
337 
TEST(OptionsTests,ParsesCompileRustInvalid_RejectHeaderOut)338 TEST(OptionsTests, ParsesCompileRustInvalid_RejectHeaderOut) {
339   string expected_error = "Header output directory is set, which does not make sense for Rust.";
340   CaptureStderr();
341   // -h options is not for Rust
342   const char* arg_with_header_dir[] = {
343       "aidl",          "--lang=rust",           kCompileCommandIncludePath, "-o src_out",
344       "-h header_out", "directory/input1.aidl", "directory/input2.aidl",    "directory/input3.aidl",
345       nullptr,
346   };
347   EXPECT_EQ(false, GetOptions(arg_with_header_dir)->Ok());
348   EXPECT_THAT(GetCapturedStderr(), testing::HasSubstr(expected_error));
349 }
350 
TEST(OptionsTests,ParsesWarningEnableAll)351 TEST(OptionsTests, ParsesWarningEnableAll) {
352   const char* args[] = {
353       "aidl", "--lang=java", "-Weverything", "--out=out", "input.aidl", nullptr,
354   };
355   auto options = GetOptions(args);
356   EXPECT_TRUE(options->Ok());
357   auto mapping = options->GetDiagnosticMapping();
358   EXPECT_EQ(DiagnosticSeverity::WARNING, mapping.Severity(DiagnosticID::interface_name));
359 }
360 
TEST(OptionsTests,ParsesWarningEnableSpecificWarning)361 TEST(OptionsTests, ParsesWarningEnableSpecificWarning) {
362   const char* args[] = {
363       "aidl", "--lang=java", "-Winterface-name", "--out=out", "input.aidl", nullptr,
364   };
365   auto options = GetOptions(args);
366   EXPECT_TRUE(options->Ok());
367   auto mapping = options->GetDiagnosticMapping();
368   EXPECT_EQ(DiagnosticSeverity::WARNING, mapping.Severity(DiagnosticID::interface_name));
369 }
370 
TEST(OptionsTests,ParsesWarningDisableSpecificWarning)371 TEST(OptionsTests, ParsesWarningDisableSpecificWarning) {
372   const char* args[] = {
373       "aidl",      "--lang=java", "-Weverything", "-Wno-interface-name",
374       "--out=out", "input.aidl",  nullptr,
375   };
376   auto options = GetOptions(args);
377   EXPECT_TRUE(options->Ok());
378   auto mapping = options->GetDiagnosticMapping();
379   EXPECT_EQ(DiagnosticSeverity::DISABLED, mapping.Severity(DiagnosticID::interface_name));
380 }
381 
TEST(OptionsTests,ParsesWarningAsErrors)382 TEST(OptionsTests, ParsesWarningAsErrors) {
383   const char* args[] = {
384       "aidl", "--lang=java", "-Werror", "-Weverything", "--out=out", "input.aidl", nullptr,
385   };
386   auto options = GetOptions(args);
387   EXPECT_TRUE(options->Ok());
388   auto mapping = options->GetDiagnosticMapping();
389   EXPECT_EQ(DiagnosticSeverity::ERROR, mapping.Severity(DiagnosticID::interface_name));
390 }
391 
TEST(OptionsTests,RejectsUnknownWarning)392 TEST(OptionsTests, RejectsUnknownWarning) {
393   const char* args[] = {
394       "aidl", "--lang=java", "-Wfoobar", "--out=out", "input.aidl", nullptr,
395   };
396   CaptureStderr();
397   auto options = GetOptions(args);
398   EXPECT_FALSE(options->Ok());
399   EXPECT_THAT(GetCapturedStderr(), testing::HasSubstr("unknown warning: foobar"));
400 }
401 
TEST(OptionsTests,CheckApi)402 TEST(OptionsTests, CheckApi) {
403   const char* args[] = {
404       "aidl", "--checkapi", "old", "new", nullptr,
405   };
406   CaptureStderr();
407   auto options = GetOptions(args);
408   EXPECT_TRUE(options->Ok());
409   EXPECT_EQ("", GetCapturedStderr());
410   EXPECT_EQ(Options::Task::CHECK_API, options->GetTask());
411   EXPECT_EQ(Options::CheckApiLevel::COMPATIBLE, options->GetCheckApiLevel());
412 }
413 
TEST(OptionsTests,CheckApiWithCompatible)414 TEST(OptionsTests, CheckApiWithCompatible) {
415   const char* args[] = {
416       "aidl", "--checkapi=compatible", "old", "new", nullptr,
417   };
418   CaptureStderr();
419   auto options = GetOptions(args);
420   EXPECT_TRUE(options->Ok());
421   EXPECT_EQ("", GetCapturedStderr());
422   EXPECT_EQ(Options::Task::CHECK_API, options->GetTask());
423   EXPECT_EQ(Options::CheckApiLevel::COMPATIBLE, options->GetCheckApiLevel());
424 }
425 
TEST(OptionsTests,CheckApiWithEqual)426 TEST(OptionsTests, CheckApiWithEqual) {
427   const char* args[] = {
428       "aidl", "--checkapi=equal", "old", "new", nullptr,
429   };
430   CaptureStderr();
431   auto options = GetOptions(args);
432   EXPECT_TRUE(options->Ok());
433   EXPECT_EQ("", GetCapturedStderr());
434   EXPECT_EQ(Options::Task::CHECK_API, options->GetTask());
435   EXPECT_EQ(Options::CheckApiLevel::EQUAL, options->GetCheckApiLevel());
436 }
437 
TEST(OptionsTests,CheckApiWithUnknown)438 TEST(OptionsTests, CheckApiWithUnknown) {
439   const char* args[] = {
440       "aidl", "--checkapi=unknown", "old", "new", nullptr,
441   };
442   CaptureStderr();
443   auto options = GetOptions(args);
444   EXPECT_FALSE(options->Ok());
445   EXPECT_THAT(GetCapturedStderr(), testing::HasSubstr("Unsupported --checkapi level: 'unknown'"));
446 }
447 
448 }  // namespace aidl
449 }  // namespace android
450