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
27static 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
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
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
81    static void setup_crash_handler() {}
82    static void defer_crash_stacktraces() {}
83#endif
84
85enum class Status { OK, Failed, Crashed, Skipped, None };
86
87struct Engine {
88    virtual ~Engine() {}
89    virtual bool spawn(std::function<Status(void)>) = 0;
90    virtual Status wait_one() = 0;
91};
92
93struct SerialEngine : Engine {
94    Status last = Status::None;
95
96    bool spawn(std::function<Status(void)> fn) override {
97        last = fn();
98        return true;
99    }
100
101    Status wait_one() override {
102        Status s = last;
103        last = Status::None;
104        return s;
105    }
106};
107
108struct ThreadEngine : Engine {
109    std::list<std::future<Status>> live;
110
111    bool spawn(std::function<Status(void)> fn) override {
112        live.push_back(std::async(std::launch::async, fn));
113        return true;
114    }
115
116    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 {
140        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
148        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
161struct StreamType {
162    const char* name;
163    std::unique_ptr<Stream> (*factory)(Options);
164};
165static std::vector<StreamType> stream_types;
166
167struct DstType {
168    const char* name;
169    std::unique_ptr<Dst> (*factory)(Options);
170};
171static std::vector<DstType> dst_types;
172
173struct ViaType {
174    const char* name;
175    std::unique_ptr<Dst> (*factory)(Options, std::unique_ptr<Dst>);
176};
177static std::vector<ViaType> via_types;
178
179int 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
362Register::Register(const char* name, std::unique_ptr<Stream> (*factory)(Options)) {
363    stream_types.push_back(StreamType{name, factory});
364}
365Register::Register(const char* name, std::unique_ptr<Dst> (*factory)(Options)) {
366    dst_types.push_back(DstType{name, factory});
367}
368Register::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
373Options::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
388std::string& Options::operator[](std::string k) { return this->kv[k]; }
389
390std::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