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