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