/* * Copyright 2017 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ // ok is an experimental test harness, maybe to replace DM. Key features: // * work is balanced across separate processes for stability and isolation; // * ok is entirely opt-in. No more maintaining huge --blacklists. #include "SkGraphics.h" #include "SkImage.h" #include "ok.h" #include #include #include #include #include #include #include #if !defined(__has_include) #define __has_include(x) 0 #endif static thread_local const char* tls_currently_running = ""; #if __has_include() && __has_include() && __has_include() #include #include #include static int log_fd = 2/*stderr*/; static void log(const char* msg) { write(log_fd, msg, strlen(msg)); } static void setup_crash_handler() { static void (*original_handlers[32])(int); for (int sig : std::vector{ SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV }) { original_handlers[sig] = signal(sig, [](int sig) { lockf(log_fd, F_LOCK, 0); log("\ncaught signal "); switch (sig) { #define CASE(s) case s: log(#s); break CASE(SIGABRT); CASE(SIGBUS); CASE(SIGFPE); CASE(SIGILL); CASE(SIGSEGV); #undef CASE } log(" while running '"); log(tls_currently_running); log("'\n"); void* stack[128]; int frames = backtrace(stack, sizeof(stack)/sizeof(*stack)); backtrace_symbols_fd(stack, frames, log_fd); lockf(log_fd, F_ULOCK, 0); signal(sig, original_handlers[sig]); raise(sig); }); } } static void defer_logging() { log_fd = fileno(tmpfile()); atexit([] { lseek(log_fd, 0, SEEK_SET); char buf[1024]; while (size_t bytes = read(log_fd, buf, sizeof(buf))) { write(2, buf, bytes); } }); } void ok_log(const char* msg) { lockf(log_fd, F_LOCK, 0); log("["); log(tls_currently_running); log("]\t"); log(msg); log("\n"); lockf(log_fd, F_ULOCK, 0); } #else static void setup_crash_handler() {} static void defer_logging() {} void ok_log(const char* msg) { fprintf(stderr, "%s\n", msg); } #endif struct Engine { virtual ~Engine() {} virtual bool spawn(std::function) = 0; virtual Status wait_one() = 0; }; struct SerialEngine : Engine { Status last = Status::None; bool spawn(std::function fn) override { last = fn(); return true; } Status wait_one() override { Status s = last; last = Status::None; return s; } }; struct ThreadEngine : Engine { std::list> live; const std::chrono::steady_clock::time_point the_past = std::chrono::steady_clock::now(); bool spawn(std::function fn) override { live.push_back(std::async(std::launch::async, fn)); return true; } Status wait_one() override { if (live.empty()) { return Status::None; } for (;;) { for (auto it = live.begin(); it != live.end(); it++) { if (it->wait_until(the_past) == std::future_status::ready) { Status s = it->get(); live.erase(it); return s; } } } } }; #if defined(_MSC_VER) using ForkEngine = ThreadEngine; #else #include #include struct ForkEngine : Engine { bool spawn(std::function fn) override { switch (fork()) { case 0: _exit((int)fn()); case -1: return false; default: return true; } } Status wait_one() override { do { int status; if (wait(&status) > 0) { return WIFEXITED(status) ? (Status)WEXITSTATUS(status) : Status::Crashed; } } while (errno == EINTR); return Status::None; } }; #endif struct StreamType { const char *name, *help; std::unique_ptr (*factory)(Options); }; static std::vector stream_types; struct DstType { const char *name, *help; std::unique_ptr (*factory)(Options); }; static std::vector dst_types; struct ViaType { const char *name, *help; std::unique_ptr (*factory)(Options, std::unique_ptr); }; static std::vector via_types; template static std::string help_for(std::vector registered) { std::string help; for (auto r : registered) { help += "\n "; help += r.name; help += ": "; help += r.help; } return help; } int main(int argc, char** argv) { SkGraphics::Init(); setup_crash_handler(); int jobs{1}; std::unique_ptr stream; std::function(void)> dst_factory = []{ // A default Dst that's enough for unit tests and not much else. struct : Dst { Status draw(Src* src) override { return src->draw(nullptr); } sk_sp image() override { return nullptr; } } dst; return move_unique(dst); }; auto help = [&] { std::string stream_help = help_for(stream_types), dst_help = help_for( dst_types), via_help = help_for( via_types); printf("%s [-j N] src[:k=v,...] dst[:k=v,...] [via[:k=v,...] ...] \n" " -j: Run at most N processes at any time. \n" " If <0, use -N threads instead. \n" " If 0, use one thread in one process. \n" " If 1 (default) or -1, auto-detect N. \n" " src: content to draw%s \n" " dst: how to draw that content%s \n" " via: wrappers around dst%s \n" " Most srcs, dsts and vias have options, e.g. skp:dir=skps sw:ct=565 \n", argv[0], stream_help.c_str(), dst_help.c_str(), via_help.c_str()); return 1; }; for (int i = 1; i < argc; i++) { if (0 == strcmp("-j", argv[i])) { jobs = atoi(argv[++i]); } if (0 == strcmp("-h", argv[i])) { return help(); } if (0 == strcmp("--help", argv[i])) { return help(); } for (auto s : stream_types) { size_t len = strlen(s.name); if (0 == strncmp(s.name, argv[i], len)) { switch (argv[i][len]) { case ':': len++; case '\0': stream = s.factory(Options{argv[i]+len}); } } } for (auto d : dst_types) { size_t len = strlen(d.name); if (0 == strncmp(d.name, argv[i], len)) { switch (argv[i][len]) { case ':': len++; case '\0': dst_factory = [=]{ return d.factory(Options{argv[i]+len}); }; } } } for (auto v : via_types) { size_t len = strlen(v.name); if (0 == strncmp(v.name, argv[i], len)) { if (!dst_factory) { return help(); } switch (argv[i][len]) { case ':': len++; case '\0': dst_factory = [=]{ return v.factory(Options{argv[i]+len}, dst_factory()); }; } } } } if (!stream) { return help(); } std::unique_ptr engine; if (jobs == 0) { engine.reset(new SerialEngine); } if (jobs > 0) { engine.reset(new ForkEngine); defer_logging(); } if (jobs < 0) { engine.reset(new ThreadEngine); jobs = -jobs; } if (jobs == 1) { jobs = std::thread::hardware_concurrency(); } int ok = 0, failed = 0, crashed = 0, skipped = 0; auto update_stats = [&](Status s) { switch (s) { case Status::OK: ok++; break; case Status::Failed: failed++; break; case Status::Crashed: crashed++; break; case Status::Skipped: skipped++; break; case Status::None: return; } const char* leader = "\r"; auto print = [&](int count, const char* label) { if (count) { printf("%s%d %s", leader, count, label); leader = ", "; } }; print(ok, "ok"); print(failed, "failed"); print(crashed, "crashed"); print(skipped, "skipped"); fflush(stdout); }; auto spawn = [&](std::function fn) { if (--jobs < 0) { update_stats(engine->wait_one()); } while (!engine->spawn(fn)) { update_stats(engine->wait_one()); } }; for (std::unique_ptr owned = stream->next(); owned; owned = stream->next()) { Src* raw = owned.release(); // Can't move std::unique_ptr into a lambda in C++11. :( spawn([=] { std::unique_ptr src{raw}; std::string name = src->name(); tls_currently_running = name.c_str(); return dst_factory()->draw(src.get()); }); } for (Status s = Status::OK; s != Status::None; ) { s = engine->wait_one(); update_stats(s); } printf("\n"); return (failed || crashed) ? 1 : 0; } Register::Register(const char* name, const char* help, std::unique_ptr (*factory)(Options)) { stream_types.push_back(StreamType{name, help, factory}); } Register::Register(const char* name, const char* help, std::unique_ptr (*factory)(Options)) { dst_types.push_back(DstType{name, help, factory}); } Register::Register(const char* name, const char* help, std::unique_ptr (*factory)(Options, std::unique_ptr)) { via_types.push_back(ViaType{name, help, factory}); } Options::Options(std::string str) { std::string k,v, *curr = &k; for (auto c : str) { switch(c) { case ',': (*this)[k] = v; curr = &(k = ""); break; case '=': curr = &(v = ""); break; default: *curr += c; } } (*this)[k] = v; } std::string& Options::operator[](std::string k) { return this->kv[k]; } std::string Options::operator()(std::string k, std::string fallback) const { for (auto it = kv.find(k); it != kv.end(); ) { return it->second; } return fallback; }