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