1 /*
2  * Copyright (C) 2019 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 <errno.h>
18 #include <getopt.h>
19 #include <inttypes.h>
20 #include <libgen.h>
21 #include <stdarg.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/wait.h>
26 #include <time.h>
27 #include <unistd.h>
28 
29 #include <string>
30 #include <vector>
31 
32 #include <android-base/chrono_utils.h>
33 #include <android-base/file.h>
34 #include <android-base/stringprintf.h>
35 #include <android-base/strings.h>
36 #include <android-base/test_utils.h>
37 
38 // Example:
39 
40 // name: unzip -n
41 // before: mkdir -p d1/d2
42 // before: echo b > d1/d2/a.txt
43 // command: unzip -q -n $FILES/zip/example.zip d1/d2/a.txt && cat d1/d2/a.txt
44 // expected-stdout:
45 // 	b
46 
47 struct Test {
48   std::string test_filename;
49   std::string name;
50   std::string command;
51   std::vector<std::string> befores;
52   std::vector<std::string> afters;
53   std::string expected_stdout;
54   std::string expected_stderr;
55   int exit_status = 0;
56 };
57 
58 static const char* g_progname;
59 static bool g_verbose;
60 
61 static const char* g_file;
62 static size_t g_line;
63 
64 enum Color { kRed, kGreen };
65 
Print(Color c,const char * lhs,const char * fmt,...)66 static void Print(Color c, const char* lhs, const char* fmt, ...) {
67   va_list ap;
68   va_start(ap, fmt);
69   if (isatty(0)) printf("%s", (c == kRed) ? "\e[31m" : "\e[32m");
70   printf("%s%s", lhs, isatty(0) ? "\e[0m" : "");
71   vfprintf(stdout, fmt, ap);
72   putchar('\n');
73   va_end(ap);
74 }
75 
Die(int error,const char * fmt,...)76 static void Die(int error, const char* fmt, ...) {
77   va_list ap;
78   va_start(ap, fmt);
79   fprintf(stderr, "%s: ", g_progname);
80   vfprintf(stderr, fmt, ap);
81   if (error != 0) fprintf(stderr, ": %s", strerror(error));
82   fprintf(stderr, "\n");
83   va_end(ap);
84   _exit(1);
85 }
86 
V(const char * fmt,...)87 static void V(const char* fmt, ...) {
88   if (!g_verbose) return;
89 
90   va_list ap;
91   va_start(ap, fmt);
92   fprintf(stderr, "           - ");
93   vfprintf(stderr, fmt, ap);
94   fprintf(stderr, "\n");
95   va_end(ap);
96 }
97 
SetField(const char * what,std::string * field,std::string_view value)98 static void SetField(const char* what, std::string* field, std::string_view value) {
99   if (!field->empty()) {
100     Die(0, "%s:%zu: %s already set to '%s'", g_file, g_line, what, field->c_str());
101   }
102   field->assign(value);
103 }
104 
105 // Similar to ConsumePrefix, but also trims, so "key:value" and "key: value"
106 // are equivalent.
Match(std::string * s,const std::string & prefix)107 static bool Match(std::string* s, const std::string& prefix) {
108   if (!android::base::StartsWith(*s, prefix)) return false;
109   s->assign(android::base::Trim(s->substr(prefix.length())));
110   return true;
111 }
112 
CollectTests(std::vector<Test> * tests,const char * test_filename)113 static void CollectTests(std::vector<Test>* tests, const char* test_filename) {
114   std::string absolute_test_filename;
115   if (!android::base::Realpath(test_filename, &absolute_test_filename)) {
116     Die(errno, "realpath '%s'", test_filename);
117   }
118 
119   std::string content;
120   if (!android::base::ReadFileToString(test_filename, &content)) {
121     Die(errno, "couldn't read '%s'", test_filename);
122   }
123 
124   size_t count = 0;
125   g_file = test_filename;
126   g_line = 0;
127   auto lines = android::base::Split(content, "\n");
128   std::unique_ptr<Test> test(new Test);
129   while (g_line < lines.size()) {
130     auto line = lines[g_line++];
131     if (line.empty() || line[0] == '#') continue;
132 
133     if (line[0] == '-') {
134       if (test->name.empty() || test->command.empty()) {
135         Die(0, "%s:%zu: each test requires both a name and a command", g_file, g_line);
136       }
137       test->test_filename = absolute_test_filename;
138       tests->push_back(*test.release());
139       test.reset(new Test);
140       ++count;
141     } else if (Match(&line, "name:")) {
142       SetField("name", &test->name, line);
143     } else if (Match(&line, "command:")) {
144       SetField("command", &test->command, line);
145     } else if (Match(&line, "before:")) {
146       test->befores.push_back(line);
147     } else if (Match(&line, "after:")) {
148       test->afters.push_back(line);
149     } else if (Match(&line, "expected-stdout:")) {
150       // Collect tab-indented lines.
151       std::string text;
152       while (g_line < lines.size() && !lines[g_line].empty() && lines[g_line][0] == '\t') {
153         text += lines[g_line++].substr(1) + "\n";
154       }
155       SetField("expected stdout", &test->expected_stdout, text);
156     } else {
157       Die(0, "%s:%zu: syntax error: \"%s\"", g_file, g_line, line.c_str());
158     }
159   }
160   if (count == 0) Die(0, "no tests found in '%s'", g_file);
161 }
162 
Plural(size_t n)163 static const char* Plural(size_t n) {
164   return (n == 1) ? "" : "s";
165 }
166 
ExitStatusToString(int status)167 static std::string ExitStatusToString(int status) {
168   if (WIFSIGNALED(status)) {
169     return android::base::StringPrintf("was killed by signal %d (%s)", WTERMSIG(status),
170                                        strsignal(WTERMSIG(status)));
171   }
172   if (WIFSTOPPED(status)) {
173     return android::base::StringPrintf("was stopped by signal %d (%s)", WSTOPSIG(status),
174                                        strsignal(WSTOPSIG(status)));
175   }
176   return android::base::StringPrintf("exited with status %d", WEXITSTATUS(status));
177 }
178 
RunCommands(const char * what,const std::vector<std::string> & commands)179 static bool RunCommands(const char* what, const std::vector<std::string>& commands) {
180   bool result = true;
181   for (auto& command : commands) {
182     V("running %s \"%s\"", what, command.c_str());
183     int exit_status = system(command.c_str());
184     if (exit_status != 0) {
185       result = false;
186       fprintf(stderr, "Command (%s) \"%s\" %s\n", what, command.c_str(),
187               ExitStatusToString(exit_status).c_str());
188     }
189   }
190   return result;
191 }
192 
CheckOutput(const char * what,std::string actual_output,const std::string & expected_output,const std::string & FILES)193 static bool CheckOutput(const char* what, std::string actual_output,
194                         const std::string& expected_output, const std::string& FILES) {
195   // Rewrite the output to reverse any expansion of $FILES.
196   actual_output = android::base::StringReplace(actual_output, FILES, "$FILES", true);
197 
198   bool result = (actual_output == expected_output);
199   if (!result) {
200     fprintf(stderr, "Incorrect %s.\nExpected:\n%s\nActual:\n%s\n", what, expected_output.c_str(),
201             actual_output.c_str());
202   }
203   return result;
204 }
205 
RunTests(const std::vector<Test> & tests)206 static int RunTests(const std::vector<Test>& tests) {
207   std::vector<std::string> failures;
208 
209   Print(kGreen, "[==========]", " Running %zu tests.", tests.size());
210   android::base::Timer total_timer;
211   for (const auto& test : tests) {
212     bool failed = false;
213 
214     Print(kGreen, "[ RUN      ]", " %s", test.name.c_str());
215     android::base::Timer test_timer;
216 
217     // Set $FILES for this test.
218     std::string FILES = android::base::Dirname(test.test_filename) + "/files";
219     V("setenv(\"FILES\", \"%s\")", FILES.c_str());
220     setenv("FILES", FILES.c_str(), 1);
221 
222     // Make a safe space to run the test.
223     TemporaryDir td;
224     V("chdir(\"%s\")", td.path);
225     if (chdir(td.path)) Die(errno, "chdir(\"%s\")", td.path);
226 
227     // Perform any setup specified for this test.
228     if (!RunCommands("before", test.befores)) failed = true;
229 
230     if (!failed) {
231       V("running command \"%s\"", test.command.c_str());
232       CapturedStdout test_stdout;
233       CapturedStderr test_stderr;
234       int exit_status = system(test.command.c_str());
235       test_stdout.Stop();
236       test_stderr.Stop();
237 
238       V("exit status %d", exit_status);
239       if (exit_status != test.exit_status) {
240         failed = true;
241         fprintf(stderr, "Incorrect exit status: expected %d but %s\n", test.exit_status,
242                 ExitStatusToString(exit_status).c_str());
243       }
244 
245       if (!CheckOutput("stdout", test_stdout.str(), test.expected_stdout, FILES)) failed = true;
246       if (!CheckOutput("stderr", test_stderr.str(), test.expected_stderr, FILES)) failed = true;
247 
248       if (!RunCommands("after", test.afters)) failed = true;
249     }
250 
251     std::stringstream duration;
252     duration << test_timer;
253     if (failed) {
254       failures.push_back(test.name);
255       Print(kRed, "[  FAILED  ]", " %s (%s)", test.name.c_str(), duration.str().c_str());
256     } else {
257       Print(kGreen, "[       OK ]", " %s (%s)", test.name.c_str(), duration.str().c_str());
258     }
259   }
260 
261   // Summarize the whole run and explicitly list all the failures.
262 
263   std::stringstream duration;
264   duration << total_timer;
265   Print(kGreen, "[==========]", " %zu tests ran. (%s total)", tests.size(), duration.str().c_str());
266 
267   size_t fail_count = failures.size();
268   size_t pass_count = tests.size() - fail_count;
269   Print(kGreen, "[  PASSED  ]", " %zu test%s.", pass_count, Plural(pass_count));
270   if (!failures.empty()) {
271     Print(kRed, "[  FAILED  ]", " %zu test%s.", fail_count, Plural(fail_count));
272     for (auto& failure : failures) {
273       Print(kRed, "[  FAILED  ]", " %s", failure.c_str());
274     }
275   }
276   return (fail_count == 0) ? 0 : 1;
277 }
278 
ShowHelp(bool full)279 static void ShowHelp(bool full) {
280   fprintf(full ? stdout : stderr, "usage: %s [-v] FILE...\n", g_progname);
281   if (!full) exit(EXIT_FAILURE);
282 
283   printf(
284       "\n"
285       "Run tests.\n"
286       "\n"
287       "-v\tVerbose (show workings)\n");
288   exit(EXIT_SUCCESS);
289 }
290 
main(int argc,char * argv[])291 int main(int argc, char* argv[]) {
292   g_progname = basename(argv[0]);
293 
294   static const struct option opts[] = {
295       {"help", no_argument, 0, 'h'},
296       {"verbose", no_argument, 0, 'v'},
297       {},
298   };
299 
300   int opt;
301   while ((opt = getopt_long(argc, argv, "hv", opts, nullptr)) != -1) {
302     switch (opt) {
303       case 'h':
304         ShowHelp(true);
305         break;
306       case 'v':
307         g_verbose = true;
308         break;
309       default:
310         ShowHelp(false);
311         break;
312     }
313   }
314 
315   argv += optind;
316   if (!*argv) Die(0, "no test files provided");
317   std::vector<Test> tests;
318   for (; *argv; ++argv) CollectTests(&tests, *argv);
319   return RunTests(tests);
320 }
321