1 /*
2 * Copyright 2017 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 // ok is an experimental test harness, maybe to replace DM. Key features:
9 // * work is balanced across separate processes for stability and isolation;
10 // * ok is entirely opt-in. No more maintaining huge --blacklists.
11
12 #include "SkGraphics.h"
13 #include "SkOSFile.h"
14 #include "ok.h"
15 #include <chrono>
16 #include <future>
17 #include <list>
18 #include <regex>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <thread>
22
23 #if !defined(__has_include)
24 #define __has_include(x) 0
25 #endif
26
27 static thread_local const char* tls_name = "";
28
29 #if __has_include(<execinfo.h>) && __has_include(<fcntl.h>) && __has_include(<signal.h>)
30 #include <execinfo.h>
31 #include <fcntl.h>
32 #include <signal.h>
33
34 static int crash_stacktrace_fd = 2/*stderr*/;
35
setup_crash_handler()36 static void setup_crash_handler() {
37 static void (*original_handlers[32])(int);
38
39 for (int sig : std::vector<int>{ SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV }) {
40 original_handlers[sig] = signal(sig, [](int sig) {
41 // To prevent interlaced output, lock the stacktrace log file until we die.
42 lockf(crash_stacktrace_fd, F_LOCK, 0);
43
44 auto ez_write = [](const char* str) {
45 write(crash_stacktrace_fd, str, strlen(str));
46 };
47 ez_write("\ncaught signal ");
48 switch (sig) {
49 #define CASE(s) case s: ez_write(#s); break
50 CASE(SIGABRT);
51 CASE(SIGBUS);
52 CASE(SIGFPE);
53 CASE(SIGILL);
54 CASE(SIGSEGV);
55 #undef CASE
56 }
57 ez_write(" while running '");
58 ez_write(tls_name);
59 ez_write("'\n");
60
61 void* stack[128];
62 int frames = backtrace(stack, sizeof(stack)/sizeof(*stack));
63 backtrace_symbols_fd(stack, frames, crash_stacktrace_fd);
64 signal(sig, original_handlers[sig]);
65 raise(sig);
66 });
67 }
68 }
69
defer_crash_stacktraces()70 static void defer_crash_stacktraces() {
71 crash_stacktrace_fd = fileno(tmpfile());
72 atexit([] {
73 lseek(crash_stacktrace_fd, 0, SEEK_SET);
74 char buf[1024];
75 while (size_t bytes = read(crash_stacktrace_fd, buf, sizeof(buf))) {
76 write(2, buf, bytes);
77 }
78 });
79 }
80 #else
setup_crash_handler()81 static void setup_crash_handler() {}
defer_crash_stacktraces()82 static void defer_crash_stacktraces() {}
83 #endif
84
85 enum class Status { OK, Failed, Crashed, Skipped, None };
86
87 struct Engine {
~EngineEngine88 virtual ~Engine() {}
89 virtual bool spawn(std::function<Status(void)>) = 0;
90 virtual Status wait_one() = 0;
91 };
92
93 struct SerialEngine : Engine {
94 Status last = Status::None;
95
spawnSerialEngine96 bool spawn(std::function<Status(void)> fn) override {
97 last = fn();
98 return true;
99 }
100
wait_oneSerialEngine101 Status wait_one() override {
102 Status s = last;
103 last = Status::None;
104 return s;
105 }
106 };
107
108 struct ThreadEngine : Engine {
109 std::list<std::future<Status>> live;
110
spawnThreadEngine111 bool spawn(std::function<Status(void)> fn) override {
112 live.push_back(std::async(std::launch::async, fn));
113 return true;
114 }
115
wait_oneThreadEngine116 Status wait_one() override {
117 if (live.empty()) {
118 return Status::None;
119 }
120
121 for (;;) {
122 for (auto it = live.begin(); it != live.end(); it++) {
123 if (it->wait_for(std::chrono::seconds::zero()) == std::future_status::ready) {
124 Status s = it->get();
125 live.erase(it);
126 return s;
127 }
128 }
129 }
130 }
131 };
132
133 #if defined(_MSC_VER)
134 using ForkEngine = ThreadEngine;
135 #else
136 #include <sys/wait.h>
137 #include <unistd.h>
138
139 struct ForkEngine : Engine {
spawnForkEngine140 bool spawn(std::function<Status(void)> fn) override {
141 switch (fork()) {
142 case 0: _exit((int)fn());
143 case -1: return false;
144 default: return true;
145 }
146 }
147
wait_oneForkEngine148 Status wait_one() override {
149 do {
150 int status;
151 if (wait(&status) > 0) {
152 return WIFEXITED(status) ? (Status)WEXITSTATUS(status)
153 : Status::Crashed;
154 }
155 } while (errno == EINTR);
156 return Status::None;
157 }
158 };
159 #endif
160
161 struct StreamType {
162 const char* name;
163 std::unique_ptr<Stream> (*factory)(Options);
164 };
165 static std::vector<StreamType> stream_types;
166
167 struct DstType {
168 const char* name;
169 std::unique_ptr<Dst> (*factory)(Options);
170 };
171 static std::vector<DstType> dst_types;
172
173 struct ViaType {
174 const char* name;
175 std::unique_ptr<Dst> (*factory)(Options, std::unique_ptr<Dst>);
176 };
177 static std::vector<ViaType> via_types;
178
main(int argc,char ** argv)179 int main(int argc, char** argv) {
180 SkGraphics::Init();
181 setup_crash_handler();
182
183 int jobs {1};
184 std::regex match {".*"};
185 std::regex search {".*"};
186 std::string write_dir {""};
187
188 std::unique_ptr<Stream> stream;
189 std::function<std::unique_ptr<Dst>(void)> dst_factory;
190
191 auto help = [&] {
192 std::string stream_names, dst_names, via_names;
193 for (auto s : stream_types) {
194 if (!stream_names.empty()) {
195 stream_names += ", ";
196 }
197 stream_names += s.name;
198 }
199 for (auto d : dst_types) {
200 if (!dst_names.empty()) {
201 dst_names += ", ";
202 }
203 dst_names += d.name;
204 }
205 for (auto v : via_types) {
206 if (!via_names.empty()) {
207 via_names += ", ";
208 }
209 via_names += v.name;
210 }
211
212 printf("%s [-j N] [-m regex] [-s regex] [-w dir] [-h] \n"
213 " src[:k=v,...] dst[:k=v,...] [via[:k=v,...] ...] \n"
214 " -j: Run at most N processes at any time. \n"
215 " If <0, use -N threads instead. \n"
216 " If 0, use one thread in one process. \n"
217 " If 1 (default) or -1, auto-detect N. \n"
218 " -m: Run only names matching regex exactly. \n"
219 " -s: Run only names matching regex anywhere. \n"
220 " -w: If set, write .pngs into dir. \n"
221 " -h: Print this message and exit. \n"
222 " src: content to draw: %s \n"
223 " dst: how to draw that content: %s \n"
224 " via: front-patches to the dst: %s \n"
225 " Some srcs, dsts and vias have options, e.g. skp:dir=skps sw:ct=565 \n",
226 argv[0], stream_names.c_str(), dst_names.c_str(), via_names.c_str());
227 return 1;
228 };
229
230 for (int i = 1; i < argc; i++) {
231 if (0 == strcmp("-j", argv[i])) { jobs = atoi(argv[++i]); }
232 if (0 == strcmp("-m", argv[i])) { match = argv[++i] ; }
233 if (0 == strcmp("-s", argv[i])) { search = argv[++i] ; }
234 if (0 == strcmp("-w", argv[i])) { write_dir = argv[++i] ; }
235 if (0 == strcmp("-h", argv[i])) { return help(); }
236
237 for (auto s : stream_types) {
238 size_t len = strlen(s.name);
239 if (0 == strncmp(s.name, argv[i], len)) {
240 switch (argv[i][len]) {
241 case ':': len++;
242 case '\0': stream = s.factory(Options{argv[i]+len});
243 }
244 }
245 }
246 for (auto d : dst_types) {
247 size_t len = strlen(d.name);
248 if (0 == strncmp(d.name, argv[i], len)) {
249 switch (argv[i][len]) {
250 case ':': len++;
251 case '\0': dst_factory = [=]{
252 return d.factory(Options{argv[i]+len});
253 };
254 }
255 }
256 }
257 for (auto v : via_types) {
258 size_t len = strlen(v.name);
259 if (0 == strncmp(v.name, argv[i], len)) {
260 if (!dst_factory) { return help(); }
261 switch (argv[i][len]) {
262 case ':': len++;
263 case '\0': dst_factory = [=]{
264 return v.factory(Options{argv[i]+len}, dst_factory());
265 };
266 }
267 }
268 }
269 }
270 if (!stream) { return help(); }
271 if (!dst_factory) {
272 // A default Dst that's enough for unit tests and not much else.
273 dst_factory = []{
274 struct : Dst {
275 bool draw(Src* src) override { return src->draw(nullptr); }
276 sk_sp<SkImage> image() override { return nullptr; }
277 } dst;
278 return move_unique(dst);
279 };
280 }
281
282 std::unique_ptr<Engine> engine;
283 if (jobs == 0) { engine.reset(new SerialEngine); }
284 if (jobs > 0) { engine.reset(new ForkEngine); defer_crash_stacktraces(); }
285 if (jobs < 0) { engine.reset(new ThreadEngine); jobs = -jobs; }
286
287 if (jobs == 1) { jobs = std::thread::hardware_concurrency(); }
288
289 if (!write_dir.empty()) {
290 sk_mkdir(write_dir.c_str());
291 }
292
293 int ok = 0, failed = 0, crashed = 0, skipped = 0;
294
295 auto update_stats = [&](Status s) {
296 switch (s) {
297 case Status::OK: ok++; break;
298 case Status::Failed: failed++; break;
299 case Status::Crashed: crashed++; break;
300 case Status::Skipped: skipped++; break;
301 case Status::None: return;
302 }
303 const char* leader = "\r";
304 auto print = [&](int count, const char* label) {
305 if (count) {
306 printf("%s%d %s", leader, count, label);
307 leader = ", ";
308 }
309 };
310 print(ok, "ok");
311 print(failed, "failed");
312 print(crashed, "crashed");
313 print(skipped, "skipped");
314 fflush(stdout);
315 };
316
317 auto spawn = [&](std::function<Status(void)> fn) {
318 if (--jobs < 0) {
319 update_stats(engine->wait_one());
320 }
321 while (!engine->spawn(fn)) {
322 update_stats(engine->wait_one());
323 }
324 };
325
326 for (std::unique_ptr<Src> owned = stream->next(); owned; owned = stream->next()) {
327 Src* raw = owned.release(); // Can't move std::unique_ptr into a lambda in C++11. :(
328 spawn([=] {
329 std::unique_ptr<Src> src{raw};
330
331 auto name = src->name();
332 tls_name = name.c_str();
333 if (!std::regex_match (name, match) ||
334 !std::regex_search(name, search)) {
335 return Status::Skipped;
336 }
337
338 auto dst = dst_factory();
339 if (!dst->draw(src.get())) {
340 return Status::Failed;
341 }
342
343 if (!write_dir.empty()) {
344 auto image = dst->image();
345 sk_sp<SkData> png{image->encode()};
346 SkFILEWStream{(write_dir + "/" + name + ".png").c_str()}
347 .write(png->data(), png->size());
348 }
349 return Status::OK;
350 });
351 }
352
353 for (Status s = Status::OK; s != Status::None; ) {
354 s = engine->wait_one();
355 update_stats(s);
356 }
357 printf("\n");
358 return (failed || crashed) ? 1 : 0;
359 }
360
361
Register(const char * name,std::unique_ptr<Stream> (* factory)(Options))362 Register::Register(const char* name, std::unique_ptr<Stream> (*factory)(Options)) {
363 stream_types.push_back(StreamType{name, factory});
364 }
Register(const char * name,std::unique_ptr<Dst> (* factory)(Options))365 Register::Register(const char* name, std::unique_ptr<Dst> (*factory)(Options)) {
366 dst_types.push_back(DstType{name, factory});
367 }
Register(const char * name,std::unique_ptr<Dst> (* factory)(Options,std::unique_ptr<Dst>))368 Register::Register(const char* name,
369 std::unique_ptr<Dst> (*factory)(Options, std::unique_ptr<Dst>)) {
370 via_types.push_back(ViaType{name, factory});
371 }
372
Options(std::string str)373 Options::Options(std::string str) {
374 std::string k,v, *curr = &k;
375 for (auto c : str) {
376 switch(c) {
377 case ',': (*this)[k] = v;
378 curr = &(k = "");
379 break;
380 case '=': curr = &(v = "");
381 break;
382 default: *curr += c;
383 }
384 }
385 (*this)[k] = v;
386 }
387
operator [](std::string k)388 std::string& Options::operator[](std::string k) { return this->kv[k]; }
389
operator ()(std::string k,std::string fallback) const390 std::string Options::operator()(std::string k, std::string fallback) const {
391 for (auto it = kv.find(k); it != kv.end(); ) {
392 return it->second;
393 }
394 return fallback;
395 }
396