Home | History | Annotate | Download | only in tools
      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 "SkImage.h"
     14 #include "ok.h"
     15 #include <chrono>
     16 #include <future>
     17 #include <list>
     18 #include <stdio.h>
     19 #include <stdlib.h>
     20 #include <thread>
     21 #include <vector>
     22 
     23 #if !defined(__has_include)
     24     #define  __has_include(x) 0
     25 #endif
     26 
     27 static thread_local const char* tls_currently_running = "";
     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 log_fd = 2/*stderr*/;
     35 
     36     static void log(const char* msg) {
     37         write(log_fd, msg, strlen(msg));
     38     }
     39 
     40     static void setup_crash_handler() {
     41         static void (*original_handlers[32])(int);
     42         for (int sig : std::vector<int>{ SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV }) {
     43             original_handlers[sig] = signal(sig, [](int sig) {
     44                 lockf(log_fd, F_LOCK, 0);
     45                     log("\ncaught signal ");
     46                     switch (sig) {
     47                     #define CASE(s) case s: log(#s); break
     48                         CASE(SIGABRT);
     49                         CASE(SIGBUS);
     50                         CASE(SIGFPE);
     51                         CASE(SIGILL);
     52                         CASE(SIGSEGV);
     53                     #undef CASE
     54                     }
     55                     log(" while running '");
     56                     log(tls_currently_running);
     57                     log("'\n");
     58 
     59                     void* stack[128];
     60                     int frames = backtrace(stack, sizeof(stack)/sizeof(*stack));
     61                     backtrace_symbols_fd(stack, frames, log_fd);
     62                 lockf(log_fd, F_ULOCK, 0);
     63 
     64                 signal(sig, original_handlers[sig]);
     65                 raise(sig);
     66             });
     67         }
     68     }
     69 
     70     static void defer_logging() {
     71         log_fd = fileno(tmpfile());
     72         atexit([] {
     73             lseek(log_fd, 0, SEEK_SET);
     74             char buf[1024];
     75             while (size_t bytes = read(log_fd, buf, sizeof(buf))) {
     76                 write(2, buf, bytes);
     77             }
     78         });
     79     }
     80 
     81     void ok_log(const char* msg) {
     82         lockf(log_fd, F_LOCK, 0);
     83             log("[");
     84             log(tls_currently_running);
     85             log("]\t");
     86             log(msg);
     87             log("\n");
     88         lockf(log_fd, F_ULOCK, 0);
     89     }
     90 
     91 #else
     92     static void setup_crash_handler() {}
     93     static void defer_logging() {}
     94 
     95     void ok_log(const char* msg) {
     96         fprintf(stderr, "%s\n", msg);
     97     }
     98 #endif
     99 
    100 struct Engine {
    101     virtual ~Engine() {}
    102     virtual bool spawn(std::function<Status(void)>) = 0;
    103     virtual Status wait_one() = 0;
    104 };
    105 
    106 struct SerialEngine : Engine {
    107     Status last = Status::None;
    108 
    109     bool spawn(std::function<Status(void)> fn) override {
    110         last = fn();
    111         return true;
    112     }
    113 
    114     Status wait_one() override {
    115         Status s = last;
    116         last = Status::None;
    117         return s;
    118     }
    119 };
    120 
    121 struct ThreadEngine : Engine {
    122     std::list<std::future<Status>> live;
    123     const std::chrono::steady_clock::time_point the_past = std::chrono::steady_clock::now();
    124 
    125     bool spawn(std::function<Status(void)> fn) override {
    126         live.push_back(std::async(std::launch::async, fn));
    127         return true;
    128     }
    129 
    130     Status wait_one() override {
    131         if (live.empty()) {
    132             return Status::None;
    133         }
    134 
    135         for (;;) {
    136             for (auto it = live.begin(); it != live.end(); it++) {
    137                 if (it->wait_until(the_past) == std::future_status::ready) {
    138                     Status s = it->get();
    139                     live.erase(it);
    140                     return s;
    141                 }
    142             }
    143         }
    144     }
    145 };
    146 
    147 #if defined(_MSC_VER)
    148     using ForkEngine = ThreadEngine;
    149 #else
    150     #include <sys/wait.h>
    151     #include <unistd.h>
    152 
    153     struct ForkEngine : Engine {
    154         bool spawn(std::function<Status(void)> fn) override {
    155             switch (fork()) {
    156                 case  0: _exit((int)fn());
    157                 case -1: return false;
    158                 default: return true;
    159             }
    160         }
    161 
    162         Status wait_one() override {
    163             do {
    164                 int status;
    165                 if (wait(&status) > 0) {
    166                     return WIFEXITED(status) ? (Status)WEXITSTATUS(status)
    167                                              : Status::Crashed;
    168                 }
    169             } while (errno == EINTR);
    170             return Status::None;
    171         }
    172     };
    173 #endif
    174 
    175 struct StreamType {
    176     const char *name, *help;
    177     std::unique_ptr<Stream> (*factory)(Options);
    178 };
    179 static std::vector<StreamType> stream_types;
    180 
    181 struct DstType {
    182     const char *name, *help;
    183     std::unique_ptr<Dst> (*factory)(Options);
    184 };
    185 static std::vector<DstType> dst_types;
    186 
    187 struct ViaType {
    188     const char *name, *help;
    189     std::unique_ptr<Dst> (*factory)(Options, std::unique_ptr<Dst>);
    190 };
    191 static std::vector<ViaType> via_types;
    192 
    193 template <typename T>
    194 static std::string help_for(std::vector<T> registered) {
    195     std::string help;
    196     for (auto r : registered) {
    197         help += "\n    ";
    198         help += r.name;
    199         help += ": ";
    200         help += r.help;
    201     }
    202     return help;
    203 }
    204 
    205 int main(int argc, char** argv) {
    206     SkGraphics::Init();
    207     setup_crash_handler();
    208 
    209     int                                       jobs{1};
    210     std::unique_ptr<Stream>                   stream;
    211     std::function<std::unique_ptr<Dst>(void)> dst_factory = []{
    212         // A default Dst that's enough for unit tests and not much else.
    213         struct : Dst {
    214             Status draw(Src* src)  override { return src->draw(nullptr); }
    215             sk_sp<SkImage> image() override { return nullptr; }
    216         } dst;
    217         return move_unique(dst);
    218     };
    219 
    220     auto help = [&] {
    221         std::string stream_help = help_for(stream_types),
    222                        dst_help = help_for(   dst_types),
    223                        via_help = help_for(   via_types);
    224 
    225         printf("%s [-j N] src[:k=v,...] dst[:k=v,...] [via[:k=v,...] ...]            \n"
    226                 "  -j: Run at most N processes at any time.                          \n"
    227                 "      If <0, use -N threads instead.                                \n"
    228                 "      If 0, use one thread in one process.                          \n"
    229                 "      If 1 (default) or -1, auto-detect N.                          \n"
    230                 " src: content to draw%s                                             \n"
    231                 " dst: how to draw that content%s                                    \n"
    232                 " via: wrappers around dst%s                                         \n"
    233                 " Most srcs, dsts and vias have options, e.g. skp:dir=skps sw:ct=565 \n",
    234                 argv[0], stream_help.c_str(), dst_help.c_str(), via_help.c_str());
    235         return 1;
    236     };
    237 
    238     for (int i = 1; i < argc; i++) {
    239         if (0 == strcmp("-j",     argv[i])) { jobs = atoi(argv[++i]); }
    240         if (0 == strcmp("-h",     argv[i])) { return help(); }
    241         if (0 == strcmp("--help", argv[i])) { return help(); }
    242 
    243         for (auto s : stream_types) {
    244             size_t len = strlen(s.name);
    245             if (0 == strncmp(s.name, argv[i], len)) {
    246                 switch (argv[i][len]) {
    247                     case  ':': len++;
    248                     case '\0': stream = s.factory(Options{argv[i]+len});
    249                 }
    250             }
    251         }
    252         for (auto d : dst_types) {
    253             size_t len = strlen(d.name);
    254             if (0 == strncmp(d.name, argv[i], len)) {
    255                 switch (argv[i][len]) {
    256                     case  ':': len++;
    257                     case '\0': dst_factory = [=]{
    258                                    return d.factory(Options{argv[i]+len});
    259                                };
    260                 }
    261             }
    262         }
    263         for (auto v : via_types) {
    264             size_t len = strlen(v.name);
    265             if (0 == strncmp(v.name, argv[i], len)) {
    266                 if (!dst_factory) { return help(); }
    267                 switch (argv[i][len]) {
    268                     case  ':': len++;
    269                     case '\0': dst_factory = [=]{
    270                                    return v.factory(Options{argv[i]+len}, dst_factory());
    271                                };
    272                 }
    273             }
    274         }
    275     }
    276     if (!stream) { return help(); }
    277 
    278     std::unique_ptr<Engine> engine;
    279     if (jobs == 0) { engine.reset(new SerialEngine);                  }
    280     if (jobs  > 0) { engine.reset(new   ForkEngine); defer_logging(); }
    281     if (jobs  < 0) { engine.reset(new ThreadEngine); jobs = -jobs;    }
    282 
    283     if (jobs == 1) { jobs = std::thread::hardware_concurrency(); }
    284 
    285     int ok = 0, failed = 0, crashed = 0, skipped = 0;
    286 
    287     auto update_stats = [&](Status s) {
    288         switch (s) {
    289             case Status::OK:      ok++;      break;
    290             case Status::Failed:  failed++;  break;
    291             case Status::Crashed: crashed++; break;
    292             case Status::Skipped: skipped++; break;
    293             case Status::None:              return;
    294         }
    295         const char* leader = "\r";
    296         auto print = [&](int count, const char* label) {
    297             if (count) {
    298                 printf("%s%d %s", leader, count, label);
    299                 leader = ", ";
    300             }
    301         };
    302         print(ok,      "ok");
    303         print(failed,  "failed");
    304         print(crashed, "crashed");
    305         print(skipped, "skipped");
    306         fflush(stdout);
    307     };
    308 
    309     auto spawn = [&](std::function<Status(void)> fn) {
    310         if (--jobs < 0) {
    311             update_stats(engine->wait_one());
    312         }
    313         while (!engine->spawn(fn)) {
    314             update_stats(engine->wait_one());
    315         }
    316     };
    317 
    318     for (std::unique_ptr<Src> owned = stream->next(); owned; owned = stream->next()) {
    319         Src* raw = owned.release();  // Can't move std::unique_ptr into a lambda in C++11. :(
    320         spawn([=] {
    321             std::unique_ptr<Src> src{raw};
    322 
    323             std::string name = src->name();
    324             tls_currently_running = name.c_str();
    325 
    326             return dst_factory()->draw(src.get());
    327         });
    328     }
    329 
    330     for (Status s = Status::OK; s != Status::None; ) {
    331         s = engine->wait_one();
    332         update_stats(s);
    333     }
    334     printf("\n");
    335     return (failed || crashed) ? 1 : 0;
    336 }
    337 
    338 
    339 Register::Register(const char* name, const char* help,
    340                    std::unique_ptr<Stream> (*factory)(Options)) {
    341     stream_types.push_back(StreamType{name, help, factory});
    342 }
    343 Register::Register(const char* name, const char* help,
    344                    std::unique_ptr<Dst> (*factory)(Options)) {
    345     dst_types.push_back(DstType{name, help, factory});
    346 }
    347 Register::Register(const char* name, const char* help,
    348                    std::unique_ptr<Dst> (*factory)(Options, std::unique_ptr<Dst>)) {
    349     via_types.push_back(ViaType{name, help, factory});
    350 }
    351 
    352 Options::Options(std::string str) {
    353     std::string k,v, *curr = &k;
    354     for (auto c : str) {
    355         switch(c) {
    356             case ',': (*this)[k] = v;
    357                       curr = &(k = "");
    358                       break;
    359             case '=': curr = &(v = "");
    360                       break;
    361             default: *curr += c;
    362         }
    363     }
    364     (*this)[k] = v;
    365 }
    366 
    367 std::string& Options::operator[](std::string k) { return this->kv[k]; }
    368 
    369 std::string Options::operator()(std::string k, std::string fallback) const {
    370     for (auto it = kv.find(k); it != kv.end(); ) {
    371         return it->second;
    372     }
    373     return fallback;
    374 }
    375