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